summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--configobj.py2514
-rw-r--r--docs/configobj.txt2863
-rw-r--r--docs/validate.txt735
-rw-r--r--extras/ConfigPersist.py242
-rw-r--r--extras/configpersist.txt266
-rw-r--r--setup.py48
-rw-r--r--test_configobj.py2085
-rw-r--r--validate.py1423
8 files changed, 10176 insertions, 0 deletions
diff --git a/configobj.py b/configobj.py
new file mode 100644
index 0000000..f28877d
--- /dev/null
+++ b/configobj.py
@@ -0,0 +1,2514 @@
+# configobj.py
+# A config file reader/writer that supports nested sections in config files.
+# Copyright (C) 2005-2008 Michael Foord, Nicola Larosa
+# E-mail: fuzzyman AT voidspace DOT org DOT uk
+# nico AT tekNico DOT net
+
+# ConfigObj 4
+# http://www.voidspace.org.uk/python/configobj.html
+
+# Released subject to the BSD License
+# Please see http://www.voidspace.org.uk/python/license.shtml
+
+# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
+# For information about bugfixes, updates and support, please join the
+# ConfigObj mailing list:
+# http://lists.sourceforge.net/lists/listinfo/configobj-develop
+# Comments, suggestions and bug reports welcome.
+
+from __future__ import generators
+
+import sys
+INTP_VER = sys.version_info[:2]
+if INTP_VER < (2, 2):
+ raise RuntimeError("Python v.2.2 or later needed")
+
+import os, re
+compiler = None
+try:
+ import compiler
+except ImportError:
+ # for IronPython
+ pass
+from types import StringTypes
+from warnings import warn
+try:
+ from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
+except ImportError:
+ # Python 2.2 does not have these
+ # UTF-8
+ BOM_UTF8 = '\xef\xbb\xbf'
+ # UTF-16, little endian
+ BOM_UTF16_LE = '\xff\xfe'
+ # UTF-16, big endian
+ BOM_UTF16_BE = '\xfe\xff'
+ if sys.byteorder == 'little':
+ # UTF-16, native endianness
+ BOM_UTF16 = BOM_UTF16_LE
+ else:
+ # UTF-16, native endianness
+ BOM_UTF16 = BOM_UTF16_BE
+
+# A dictionary mapping BOM to
+# the encoding to decode with, and what to set the
+# encoding attribute to.
+BOMS = {
+ BOM_UTF8: ('utf_8', None),
+ BOM_UTF16_BE: ('utf16_be', 'utf_16'),
+ BOM_UTF16_LE: ('utf16_le', 'utf_16'),
+ BOM_UTF16: ('utf_16', 'utf_16'),
+ }
+# All legal variants of the BOM codecs.
+# TODO: the list of aliases is not meant to be exhaustive, is there a
+# better way ?
+BOM_LIST = {
+ 'utf_16': 'utf_16',
+ 'u16': 'utf_16',
+ 'utf16': 'utf_16',
+ 'utf-16': 'utf_16',
+ 'utf16_be': 'utf16_be',
+ 'utf_16_be': 'utf16_be',
+ 'utf-16be': 'utf16_be',
+ 'utf16_le': 'utf16_le',
+ 'utf_16_le': 'utf16_le',
+ 'utf-16le': 'utf16_le',
+ 'utf_8': 'utf_8',
+ 'u8': 'utf_8',
+ 'utf': 'utf_8',
+ 'utf8': 'utf_8',
+ 'utf-8': 'utf_8',
+ }
+
+# Map of encodings to the BOM to write.
+BOM_SET = {
+ 'utf_8': BOM_UTF8,
+ 'utf_16': BOM_UTF16,
+ 'utf16_be': BOM_UTF16_BE,
+ 'utf16_le': BOM_UTF16_LE,
+ None: BOM_UTF8
+ }
+
+
+def match_utf8(encoding):
+ return BOM_LIST.get(encoding.lower()) == 'utf_8'
+
+
+# Quote strings used for writing values
+squot = "'%s'"
+dquot = '"%s"'
+noquot = "%s"
+wspace_plus = ' \r\n\v\t\'"'
+tsquot = '"""%s"""'
+tdquot = "'''%s'''"
+
+try:
+ enumerate
+except NameError:
+ def enumerate(obj):
+ """enumerate for Python 2.2."""
+ i = -1
+ for item in obj:
+ i += 1
+ yield i, item
+
+try:
+ True, False
+except NameError:
+ True, False = 1, 0
+
+
+__version__ = '4.5.3'
+
+__revision__ = '$Id: configobj.py 156 2006-01-31 14:57:08Z fuzzyman $'
+
+__docformat__ = "restructuredtext en"
+
+__all__ = (
+ '__version__',
+ 'DEFAULT_INDENT_TYPE',
+ 'DEFAULT_INTERPOLATION',
+ 'ConfigObjError',
+ 'NestingError',
+ 'ParseError',
+ 'DuplicateError',
+ 'ConfigspecError',
+ 'ConfigObj',
+ 'SimpleVal',
+ 'InterpolationError',
+ 'InterpolationLoopError',
+ 'MissingInterpolationOption',
+ 'RepeatSectionError',
+ 'ReloadError',
+ 'UnreprError',
+ 'UnknownType',
+ '__docformat__',
+ 'flatten_errors',
+)
+
+DEFAULT_INTERPOLATION = 'configparser'
+DEFAULT_INDENT_TYPE = ' '
+MAX_INTERPOL_DEPTH = 10
+
+OPTION_DEFAULTS = {
+ 'interpolation': True,
+ 'raise_errors': False,
+ 'list_values': True,
+ 'create_empty': False,
+ 'file_error': False,
+ 'configspec': None,
+ 'stringify': True,
+ # option may be set to one of ('', ' ', '\t')
+ 'indent_type': None,
+ 'encoding': None,
+ 'default_encoding': None,
+ 'unrepr': False,
+ 'write_empty_values': False,
+}
+
+
+
+def getObj(s):
+ s = "a=" + s
+ if compiler is None:
+ raise ImportError('compiler module not available')
+ p = compiler.parse(s)
+ return p.getChildren()[1].getChildren()[0].getChildren()[1]
+
+
+class UnknownType(Exception):
+ pass
+
+
+class Builder(object):
+
+ def build(self, o):
+ m = getattr(self, 'build_' + o.__class__.__name__, None)
+ if m is None:
+ raise UnknownType(o.__class__.__name__)
+ return m(o)
+
+ def build_List(self, o):
+ return map(self.build, o.getChildren())
+
+ def build_Const(self, o):
+ return o.value
+
+ def build_Dict(self, o):
+ d = {}
+ i = iter(map(self.build, o.getChildren()))
+ for el in i:
+ d[el] = i.next()
+ return d
+
+ def build_Tuple(self, o):
+ return tuple(self.build_List(o))
+
+ def build_Name(self, o):
+ if o.name == 'None':
+ return None
+ if o.name == 'True':
+ return True
+ if o.name == 'False':
+ return False
+
+ # An undefined Name
+ raise UnknownType('Undefined Name')
+
+ def build_Add(self, o):
+ real, imag = map(self.build_Const, o.getChildren())
+ try:
+ real = float(real)
+ except TypeError:
+ raise UnknownType('Add')
+ if not isinstance(imag, complex) or imag.real != 0.0:
+ raise UnknownType('Add')
+ return real+imag
+
+ def build_Getattr(self, o):
+ parent = self.build(o.expr)
+ return getattr(parent, o.attrname)
+
+ def build_UnarySub(self, o):
+ return -self.build_Const(o.getChildren()[0])
+
+ def build_UnaryAdd(self, o):
+ return self.build_Const(o.getChildren()[0])
+
+
+_builder = Builder()
+
+
+def unrepr(s):
+ if not s:
+ return s
+ return _builder.build(getObj(s))
+
+
+
+class ConfigObjError(SyntaxError):
+ """
+ This is the base class for all errors that ConfigObj raises.
+ It is a subclass of SyntaxError.
+ """
+ def __init__(self, message='', line_number=None, line=''):
+ self.line = line
+ self.line_number = line_number
+ SyntaxError.__init__(self, message)
+
+
+class NestingError(ConfigObjError):
+ """
+ This error indicates a level of nesting that doesn't match.
+ """
+
+
+class ParseError(ConfigObjError):
+ """
+ This error indicates that a line is badly written.
+ It is neither a valid ``key = value`` line,
+ nor a valid section marker line.
+ """
+
+
+class ReloadError(IOError):
+ """
+ A 'reload' operation failed.
+ This exception is a subclass of ``IOError``.
+ """
+ def __init__(self):
+ IOError.__init__(self, 'reload failed, filename is not set.')
+
+
+class DuplicateError(ConfigObjError):
+ """
+ The keyword or section specified already exists.
+ """
+
+
+class ConfigspecError(ConfigObjError):
+ """
+ An error occured whilst parsing a configspec.
+ """
+
+
+class InterpolationError(ConfigObjError):
+ """Base class for the two interpolation errors."""
+
+
+class InterpolationLoopError(InterpolationError):
+ """Maximum interpolation depth exceeded in string interpolation."""
+
+ def __init__(self, option):
+ InterpolationError.__init__(
+ self,
+ 'interpolation loop detected in value "%s".' % option)
+
+
+class RepeatSectionError(ConfigObjError):
+ """
+ This error indicates additional sections in a section with a
+ ``__many__`` (repeated) section.
+ """
+
+
+class MissingInterpolationOption(InterpolationError):
+ """A value specified for interpolation was missing."""
+
+ def __init__(self, option):
+ InterpolationError.__init__(
+ self,
+ 'missing option "%s" in interpolation.' % option)
+
+
+class UnreprError(ConfigObjError):
+ """An error parsing in unrepr mode."""
+
+
+
+class InterpolationEngine(object):
+ """
+ A helper class to help perform string interpolation.
+
+ This class is an abstract base class; its descendants perform
+ the actual work.
+ """
+
+ # compiled regexp to use in self.interpolate()
+ _KEYCRE = re.compile(r"%\(([^)]*)\)s")
+
+ def __init__(self, section):
+ # the Section instance that "owns" this engine
+ self.section = section
+
+
+ def interpolate(self, key, value):
+ def recursive_interpolate(key, value, section, backtrail):
+ """The function that does the actual work.
+
+ ``value``: the string we're trying to interpolate.
+ ``section``: the section in which that string was found
+ ``backtrail``: a dict to keep track of where we've been,
+ to detect and prevent infinite recursion loops
+
+ This is similar to a depth-first-search algorithm.
+ """
+ # Have we been here already?
+ if backtrail.has_key((key, section.name)):
+ # Yes - infinite loop detected
+ raise InterpolationLoopError(key)
+ # Place a marker on our backtrail so we won't come back here again
+ backtrail[(key, section.name)] = 1
+
+ # Now start the actual work
+ match = self._KEYCRE.search(value)
+ while match:
+ # The actual parsing of the match is implementation-dependent,
+ # so delegate to our helper function
+ k, v, s = self._parse_match(match)
+ if k is None:
+ # That's the signal that no further interpolation is needed
+ replacement = v
+ else:
+ # Further interpolation may be needed to obtain final value
+ replacement = recursive_interpolate(k, v, s, backtrail)
+ # Replace the matched string with its final value
+ start, end = match.span()
+ value = ''.join((value[:start], replacement, value[end:]))
+ new_search_start = start + len(replacement)
+ # Pick up the next interpolation key, if any, for next time
+ # through the while loop
+ match = self._KEYCRE.search(value, new_search_start)
+
+ # Now safe to come back here again; remove marker from backtrail
+ del backtrail[(key, section.name)]
+
+ return value
+
+ # Back in interpolate(), all we have to do is kick off the recursive
+ # function with appropriate starting values
+ value = recursive_interpolate(key, value, self.section, {})
+ return value
+
+
+ def _fetch(self, key):
+ """Helper function to fetch values from owning section.
+
+ Returns a 2-tuple: the value, and the section where it was found.
+ """
+ # switch off interpolation before we try and fetch anything !
+ save_interp = self.section.main.interpolation
+ self.section.main.interpolation = False
+
+ # Start at section that "owns" this InterpolationEngine
+ current_section = self.section
+ while True:
+ # try the current section first
+ val = current_section.get(key)
+ if val is not None:
+ break
+ # try "DEFAULT" next
+ val = current_section.get('DEFAULT', {}).get(key)
+ if val is not None:
+ break
+ # move up to parent and try again
+ # top-level's parent is itself
+ if current_section.parent is current_section:
+ # reached top level, time to give up
+ break
+ current_section = current_section.parent
+
+ # restore interpolation to previous value before returning
+ self.section.main.interpolation = save_interp
+ if val is None:
+ raise MissingInterpolationOption(key)
+ return val, current_section
+
+
+ def _parse_match(self, match):
+ """Implementation-dependent helper function.
+
+ Will be passed a match object corresponding to the interpolation
+ key we just found (e.g., "%(foo)s" or "$foo"). Should look up that
+ key in the appropriate config file section (using the ``_fetch()``
+ helper function) and return a 3-tuple: (key, value, section)
+
+ ``key`` is the name of the key we're looking for
+ ``value`` is the value found for that key
+ ``section`` is a reference to the section where it was found
+
+ ``key`` and ``section`` should be None if no further
+ interpolation should be performed on the resulting value
+ (e.g., if we interpolated "$$" and returned "$").
+ """
+ raise NotImplementedError()
+
+
+
+class ConfigParserInterpolation(InterpolationEngine):
+ """Behaves like ConfigParser."""
+ _KEYCRE = re.compile(r"%\(([^)]*)\)s")
+
+ def _parse_match(self, match):
+ key = match.group(1)
+ value, section = self._fetch(key)
+ return key, value, section
+
+
+
+class TemplateInterpolation(InterpolationEngine):
+ """Behaves like string.Template."""
+ _delimiter = '$'
+ _KEYCRE = re.compile(r"""
+ \$(?:
+ (?P<escaped>\$) | # Two $ signs
+ (?P<named>[_a-z][_a-z0-9]*) | # $name format
+ {(?P<braced>[^}]*)} # ${name} format
+ )
+ """, re.IGNORECASE | re.VERBOSE)
+
+ def _parse_match(self, match):
+ # Valid name (in or out of braces): fetch value from section
+ key = match.group('named') or match.group('braced')
+ if key is not None:
+ value, section = self._fetch(key)
+ return key, value, section
+ # Escaped delimiter (e.g., $$): return single delimiter
+ if match.group('escaped') is not None:
+ # Return None for key and section to indicate it's time to stop
+ return None, self._delimiter, None
+ # Anything else: ignore completely, just return it unchanged
+ return None, match.group(), None
+
+
+interpolation_engines = {
+ 'configparser': ConfigParserInterpolation,
+ 'template': TemplateInterpolation,
+}
+
+
+def __newobj__(cls, *args):
+ # Hack for pickle
+ return cls.__new__(cls, *args)
+
+class Section(dict):
+ """
+ A dictionary-like object that represents a section in a config file.
+
+ It does string interpolation if the 'interpolation' attribute
+ of the 'main' object is set to True.
+
+ Interpolation is tried first from this object, then from the 'DEFAULT'
+ section of this object, next from the parent and its 'DEFAULT' section,
+ and so on until the main object is reached.
+
+ A Section will behave like an ordered dictionary - following the
+ order of the ``scalars`` and ``sections`` attributes.
+ You can use this to change the order of members.
+
+ Iteration follows the order: scalars, then sections.
+ """
+
+
+ def __setstate__(self, state):
+ dict.update(self, state[0])
+ self.__dict__.update(state[1])
+
+ def __reduce__(self):
+ state = (dict(self), self.__dict__)
+ return (__newobj__, (self.__class__,), state)
+
+
+ def __init__(self, parent, depth, main, indict=None, name=None):
+ """
+ * parent is the section above
+ * depth is the depth level of this section
+ * main is the main ConfigObj
+ * indict is a dictionary to initialise the section with
+ """
+ if indict is None:
+ indict = {}
+ dict.__init__(self)
+ # used for nesting level *and* interpolation
+ self.parent = parent
+ # used for the interpolation attribute
+ self.main = main
+ # level of nesting depth of this Section
+ self.depth = depth
+ # purely for information
+ self.name = name
+ #
+ self._initialise()
+ # we do this explicitly so that __setitem__ is used properly
+ # (rather than just passing to ``dict.__init__``)
+ for entry, value in indict.iteritems():
+ self[entry] = value
+
+
+ def _initialise(self):
+ # the sequence of scalar values in this Section
+ self.scalars = []
+ # the sequence of sections in this Section
+ self.sections = []
+ # for comments :-)
+ self.comments = {}
+ self.inline_comments = {}
+ # the configspec
+ self.configspec = None
+ # for defaults
+ self.defaults = []
+ self.default_values = {}
+
+
+ def _interpolate(self, key, value):
+ try:
+ # do we already have an interpolation engine?
+ engine = self._interpolation_engine
+ except AttributeError:
+ # not yet: first time running _interpolate(), so pick the engine
+ name = self.main.interpolation
+ if name == True: # note that "if name:" would be incorrect here
+ # backwards-compatibility: interpolation=True means use default
+ name = DEFAULT_INTERPOLATION
+ name = name.lower() # so that "Template", "template", etc. all work
+ class_ = interpolation_engines.get(name, None)
+ if class_ is None:
+ # invalid value for self.main.interpolation
+ self.main.interpolation = False
+ return value
+ else:
+ # save reference to engine so we don't have to do this again
+ engine = self._interpolation_engine = class_(self)
+ # let the engine do the actual work
+ return engine.interpolate(key, value)
+
+
+ def __getitem__(self, key):
+ """Fetch the item and do string interpolation."""
+ val = dict.__getitem__(self, key)
+ if self.main.interpolation and isinstance(val, StringTypes):
+ return self._interpolate(key, val)
+ return val
+
+
+ def __setitem__(self, key, value, unrepr=False):
+ """
+ Correctly set a value.
+
+ Making dictionary values Section instances.
+ (We have to special case 'Section' instances - which are also dicts)
+
+ Keys must be strings.
+ Values need only be strings (or lists of strings) if
+ ``main.stringify`` is set.
+
+ ``unrepr`` must be set when setting a value to a dictionary, without
+ creating a new sub-section.
+ """
+ if not isinstance(key, StringTypes):
+ raise ValueError('The key "%s" is not a string.' % key)
+
+ # add the comment
+ if not self.comments.has_key(key):
+ self.comments[key] = []
+ self.inline_comments[key] = ''
+ # remove the entry from defaults
+ if key in self.defaults:
+ self.defaults.remove(key)
+ #
+ if isinstance(value, Section):
+ if not self.has_key(key):
+ self.sections.append(key)
+ dict.__setitem__(self, key, value)
+ elif isinstance(value, dict) and not unrepr:
+ # First create the new depth level,
+ # then create the section
+ if not self.has_key(key):
+ self.sections.append(key)
+ new_depth = self.depth + 1
+ dict.__setitem__(
+ self,
+ key,
+ Section(
+ self,
+ new_depth,
+ self.main,
+ indict=value,
+ name=key))
+ else:
+ if not self.has_key(key):
+ self.scalars.append(key)
+ if not self.main.stringify:
+ if isinstance(value, StringTypes):
+ pass
+ elif isinstance(value, (list, tuple)):
+ for entry in value:
+ if not isinstance(entry, StringTypes):
+ raise TypeError('Value is not a string "%s".' % entry)
+ else:
+ raise TypeError('Value is not a string "%s".' % value)
+ dict.__setitem__(self, key, value)
+
+
+ def __delitem__(self, key):
+ """Remove items from the sequence when deleting."""
+ dict. __delitem__(self, key)
+ if key in self.scalars:
+ self.scalars.remove(key)
+ else:
+ self.sections.remove(key)
+ del self.comments[key]
+ del self.inline_comments[key]
+
+
+ def get(self, key, default=None):
+ """A version of ``get`` that doesn't bypass string interpolation."""
+ try:
+ return self[key]
+ except KeyError:
+ return default
+
+
+ def update(self, indict):
+ """
+ A version of update that uses our ``__setitem__``.
+ """
+ for entry in indict:
+ self[entry] = indict[entry]
+
+
+ def pop(self, key, *args):
+ """
+ 'D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
+ If key is not found, d is returned if given, otherwise KeyError is raised'
+ """
+ val = dict.pop(self, key, *args)
+ if key in self.scalars:
+ del self.comments[key]
+ del self.inline_comments[key]
+ self.scalars.remove(key)
+ elif key in self.sections:
+ del self.comments[key]
+ del self.inline_comments[key]
+ self.sections.remove(key)
+ if self.main.interpolation and isinstance(val, StringTypes):
+ return self._interpolate(key, val)
+ return val
+
+
+ def popitem(self):
+ """Pops the first (key,val)"""
+ sequence = (self.scalars + self.sections)
+ if not sequence:
+ raise KeyError(": 'popitem(): dictionary is empty'")
+ key = sequence[0]
+ val = self[key]
+ del self[key]
+ return key, val
+
+
+ def clear(self):
+ """
+ A version of clear that also affects scalars/sections
+ Also clears comments and configspec.
+
+ Leaves other attributes alone :
+ depth/main/parent are not affected
+ """
+ dict.clear(self)
+ self.scalars = []
+ self.sections = []
+ self.comments = {}
+ self.inline_comments = {}
+ self.configspec = None
+
+
+ def setdefault(self, key, default=None):
+ """A version of setdefault that sets sequence if appropriate."""
+ try:
+ return self[key]
+ except KeyError:
+ self[key] = default
+ return self[key]
+
+
+ def items(self):
+ """D.items() -> list of D's (key, value) pairs, as 2-tuples"""
+ return zip((self.scalars + self.sections), self.values())
+
+
+ def keys(self):
+ """D.keys() -> list of D's keys"""
+ return (self.scalars + self.sections)
+
+
+ def values(self):
+ """D.values() -> list of D's values"""
+ return [self[key] for key in (self.scalars + self.sections)]
+
+
+ def iteritems(self):
+ """D.iteritems() -> an iterator over the (key, value) items of D"""
+ return iter(self.items())
+
+
+ def iterkeys(self):
+ """D.iterkeys() -> an iterator over the keys of D"""
+ return iter((self.scalars + self.sections))
+
+ __iter__ = iterkeys
+
+
+ def itervalues(self):
+ """D.itervalues() -> an iterator over the values of D"""
+ return iter(self.values())
+
+
+ def __repr__(self):
+ """x.__repr__() <==> repr(x)"""
+ return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key])))
+ for key in (self.scalars + self.sections)])
+
+ __str__ = __repr__
+ __str__.__doc__ = "x.__str__() <==> str(x)"
+
+
+ # Extra methods - not in a normal dictionary
+
+ def dict(self):
+ """
+ Return a deepcopy of self as a dictionary.
+
+ All members that are ``Section`` instances are recursively turned to
+ ordinary dictionaries - by calling their ``dict`` method.
+
+ >>> n = a.dict()
+ >>> n == a
+ 1
+ >>> n is a
+ 0
+ """
+ newdict = {}
+ for entry in self:
+ this_entry = self[entry]
+ if isinstance(this_entry, Section):
+ this_entry = this_entry.dict()
+ elif isinstance(this_entry, list):
+ # create a copy rather than a reference
+ this_entry = list(this_entry)
+ elif isinstance(this_entry, tuple):
+ # create a copy rather than a reference
+ this_entry = tuple(this_entry)
+ newdict[entry] = this_entry
+ return newdict
+
+
+ def merge(self, indict):
+ """
+ A recursive update - useful for merging config files.
+
+ >>> a = '''[section1]
+ ... option1 = True
+ ... [[subsection]]
+ ... more_options = False
+ ... # end of file'''.splitlines()
+ >>> b = '''# File is user.ini
+ ... [section1]
+ ... option1 = False
+ ... # end of file'''.splitlines()
+ >>> c1 = ConfigObj(b)
+ >>> c2 = ConfigObj(a)
+ >>> c2.merge(c1)
+ >>> c2
+ {'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}
+ """
+ for key, val in indict.items():
+ if (key in self and isinstance(self[key], dict) and
+ isinstance(val, dict)):
+ self[key].merge(val)
+ else:
+ self[key] = val
+
+
+ def rename(self, oldkey, newkey):
+ """
+ Change a keyname to another, without changing position in sequence.
+
+ Implemented so that transformations can be made on keys,
+ as well as on values. (used by encode and decode)
+
+ Also renames comments.
+ """
+ if oldkey in self.scalars:
+ the_list = self.scalars
+ elif oldkey in self.sections:
+ the_list = self.sections
+ else:
+ raise KeyError('Key "%s" not found.' % oldkey)
+ pos = the_list.index(oldkey)
+ #
+ val = self[oldkey]
+ dict.__delitem__(self, oldkey)
+ dict.__setitem__(self, newkey, val)
+ the_list.remove(oldkey)
+ the_list.insert(pos, newkey)
+ comm = self.comments[oldkey]
+ inline_comment = self.inline_comments[oldkey]
+ del self.comments[oldkey]
+ del self.inline_comments[oldkey]
+ self.comments[newkey] = comm
+ self.inline_comments[newkey] = inline_comment
+
+
+ def walk(self, function, raise_errors=True,
+ call_on_sections=False, **keywargs):
+ """
+ Walk every member and call a function on the keyword and value.
+
+ Return a dictionary of the return values
+
+ If the function raises an exception, raise the errror
+ unless ``raise_errors=False``, in which case set the return value to
+ ``False``.
+
+ Any unrecognised keyword arguments you pass to walk, will be pased on
+ to the function you pass in.
+
+ Note: if ``call_on_sections`` is ``True`` then - on encountering a
+ subsection, *first* the function is called for the *whole* subsection,
+ and then recurses into it's members. This means your function must be
+ able to handle strings, dictionaries and lists. This allows you
+ to change the key of subsections as well as for ordinary members. The
+ return value when called on the whole subsection has to be discarded.
+
+ See the encode and decode methods for examples, including functions.
+
+ .. admonition:: caution
+
+ You can use ``walk`` to transform the names of members of a section
+ but you mustn't add or delete members.
+
+ >>> config = '''[XXXXsection]
+ ... XXXXkey = XXXXvalue'''.splitlines()
+ >>> cfg = ConfigObj(config)
+ >>> cfg
+ {'XXXXsection': {'XXXXkey': 'XXXXvalue'}}
+ >>> def transform(section, key):
+ ... val = section[key]
+ ... newkey = key.replace('XXXX', 'CLIENT1')
+ ... section.rename(key, newkey)
+ ... if isinstance(val, (tuple, list, dict)):
+ ... pass
+ ... else:
+ ... val = val.replace('XXXX', 'CLIENT1')
+ ... section[newkey] = val
+ >>> cfg.walk(transform, call_on_sections=True)
+ {'CLIENT1section': {'CLIENT1key': None}}
+ >>> cfg
+ {'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}
+ """
+ out = {}
+ # scalars first
+ for i in range(len(self.scalars)):
+ entry = self.scalars[i]
+ try:
+ val = function(self, entry, **keywargs)
+ # bound again in case name has changed
+ entry = self.scalars[i]
+ out[entry] = val
+ except Exception:
+ if raise_errors:
+ raise
+ else:
+ entry = self.scalars[i]
+ out[entry] = False
+ # then sections
+ for i in range(len(self.sections)):
+ entry = self.sections[i]
+ if call_on_sections:
+ try:
+ function(self, entry, **keywargs)
+ except Exception:
+ if raise_errors:
+ raise
+ else:
+ entry = self.sections[i]
+ out[entry] = False
+ # bound again in case name has changed
+ entry = self.sections[i]
+ # previous result is discarded
+ out[entry] = self[entry].walk(
+ function,
+ raise_errors=raise_errors,
+ call_on_sections=call_on_sections,
+ **keywargs)
+ return out
+
+
+ def decode(self, encoding):
+ """
+ Decode all strings and values to unicode, using the specified encoding.
+
+ Works with subsections and list values.
+
+ Uses the ``walk`` method.
+
+ Testing ``encode`` and ``decode``.
+ >>> m = ConfigObj(a)
+ >>> m.decode('ascii')
+ >>> def testuni(val):
+ ... for entry in val:
+ ... if not isinstance(entry, unicode):
+ ... print >> sys.stderr, type(entry)
+ ... raise AssertionError, 'decode failed.'
+ ... if isinstance(val[entry], dict):
+ ... testuni(val[entry])
+ ... elif not isinstance(val[entry], unicode):
+ ... raise AssertionError, 'decode failed.'
+ >>> testuni(m)
+ >>> m.encode('ascii')
+ >>> a == m
+ 1
+ """
+ warn('use of ``decode`` is deprecated.', DeprecationWarning)
+ def decode(section, key, encoding=encoding, warn=True):
+ """ """
+ val = section[key]
+ if isinstance(val, (list, tuple)):
+ newval = []
+ for entry in val:
+ newval.append(entry.decode(encoding))
+ elif isinstance(val, dict):
+ newval = val
+ else:
+ newval = val.decode(encoding)
+ newkey = key.decode(encoding)
+ section.rename(key, newkey)
+ section[newkey] = newval
+ # using ``call_on_sections`` allows us to modify section names
+ self.walk(decode, call_on_sections=True)
+
+
+ def encode(self, encoding):
+ """
+ Encode all strings and values from unicode,
+ using the specified encoding.
+
+ Works with subsections and list values.
+ Uses the ``walk`` method.
+ """
+ warn('use of ``encode`` is deprecated.', DeprecationWarning)
+ def encode(section, key, encoding=encoding):
+ """ """
+ val = section[key]
+ if isinstance(val, (list, tuple)):
+ newval = []
+ for entry in val:
+ newval.append(entry.encode(encoding))
+ elif isinstance(val, dict):
+ newval = val
+ else:
+ newval = val.encode(encoding)
+ newkey = key.encode(encoding)
+ section.rename(key, newkey)
+ section[newkey] = newval
+ self.walk(encode, call_on_sections=True)
+
+
+ def istrue(self, key):
+ """A deprecated version of ``as_bool``."""
+ warn('use of ``istrue`` is deprecated. Use ``as_bool`` method '
+ 'instead.', DeprecationWarning)
+ return self.as_bool(key)
+
+
+ def as_bool(self, key):
+ """
+ Accepts a key as input. The corresponding value must be a string or
+ the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to
+ retain compatibility with Python 2.2.
+
+ If the string is one of ``True``, ``On``, ``Yes``, or ``1`` it returns
+ ``True``.
+
+ If the string is one of ``False``, ``Off``, ``No``, or ``0`` it returns
+ ``False``.
+
+ ``as_bool`` is not case sensitive.
+
+ Any other input will raise a ``ValueError``.
+
+ >>> a = ConfigObj()
+ >>> a['a'] = 'fish'
+ >>> a.as_bool('a')
+ Traceback (most recent call last):
+ ValueError: Value "fish" is neither True nor False
+ >>> a['b'] = 'True'
+ >>> a.as_bool('b')
+ 1
+ >>> a['b'] = 'off'
+ >>> a.as_bool('b')
+ 0
+ """
+ val = self[key]
+ if val == True:
+ return True
+ elif val == False:
+ return False
+ else:
+ try:
+ if not isinstance(val, StringTypes):
+ # TODO: Why do we raise a KeyError here?
+ raise KeyError()
+ else:
+ return self.main._bools[val.lower()]
+ except KeyError:
+ raise ValueError('Value "%s" is neither True nor False' % val)
+
+
+ def as_int(self, key):
+ """
+ A convenience method which coerces the specified value to an integer.
+
+ If the value is an invalid literal for ``int``, a ``ValueError`` will
+ be raised.
+
+ >>> a = ConfigObj()
+ >>> a['a'] = 'fish'
+ >>> a.as_int('a')
+ Traceback (most recent call last):
+ ValueError: invalid literal for int(): fish
+ >>> a['b'] = '1'
+ >>> a.as_int('b')
+ 1
+ >>> a['b'] = '3.2'
+ >>> a.as_int('b')
+ Traceback (most recent call last):
+ ValueError: invalid literal for int(): 3.2
+ """
+ return int(self[key])
+
+
+ def as_float(self, key):
+ """
+ A convenience method which coerces the specified value to a float.
+
+ If the value is an invalid literal for ``float``, a ``ValueError`` will
+ be raised.
+
+ >>> a = ConfigObj()
+ >>> a['a'] = 'fish'
+ >>> a.as_float('a')
+ Traceback (most recent call last):
+ ValueError: invalid literal for float(): fish
+ >>> a['b'] = '1'
+ >>> a.as_float('b')
+ 1.0
+ >>> a['b'] = '3.2'
+ >>> a.as_float('b')
+ 3.2000000000000002
+ """
+ return float(self[key])
+
+
+ def restore_default(self, key):
+ """
+ Restore (and return) default value for the specified key.
+
+ This method will only work for a ConfigObj that was created
+ with a configspec and has been validated.
+
+ If there is no default value for this key, ``KeyError`` is raised.
+ """
+ default = self.default_values[key]
+ dict.__setitem__(self, key, default)
+ if key not in self.defaults:
+ self.defaults.append(key)
+ return default
+
+
+ def restore_defaults(self):
+ """
+ Recursively restore default values to all members
+ that have them.
+
+ This method will only work for a ConfigObj that was created
+ with a configspec and has been validated.
+
+ It doesn't delete or modify entries without default values.
+ """
+ for key in self.default_values:
+ self.restore_default(key)
+
+ for section in self.sections:
+ self[section].restore_defaults()
+
+
+class ConfigObj(Section):
+ """An object to read, create, and write config files."""
+
+ _keyword = re.compile(r'''^ # line start
+ (\s*) # indentation
+ ( # keyword
+ (?:".*?")| # double quotes
+ (?:'.*?')| # single quotes
+ (?:[^'"=].*?) # no quotes
+ )
+ \s*=\s* # divider
+ (.*) # value (including list values and comments)
+ $ # line end
+ ''',
+ re.VERBOSE)
+
+ _sectionmarker = re.compile(r'''^
+ (\s*) # 1: indentation
+ ((?:\[\s*)+) # 2: section marker open
+ ( # 3: section name open
+ (?:"\s*\S.*?\s*")| # at least one non-space with double quotes
+ (?:'\s*\S.*?\s*')| # at least one non-space with single quotes
+ (?:[^'"\s].*?) # at least one non-space unquoted
+ ) # section name close
+ ((?:\s*\])+) # 4: section marker close
+ \s*(\#.*)? # 5: optional comment
+ $''',
+ re.VERBOSE)
+
+ # this regexp pulls list values out as a single string
+ # or single values and comments
+ # FIXME: this regex adds a '' to the end of comma terminated lists
+ # workaround in ``_handle_value``
+ _valueexp = re.compile(r'''^
+ (?:
+ (?:
+ (
+ (?:
+ (?:
+ (?:".*?")| # double quotes
+ (?:'.*?')| # single quotes
+ (?:[^'",\#][^,\#]*?) # unquoted
+ )
+ \s*,\s* # comma
+ )* # match all list items ending in a comma (if any)
+ )
+ (
+ (?:".*?")| # double quotes
+ (?:'.*?')| # single quotes
+ (?:[^'",\#\s][^,]*?)| # unquoted
+ (?:(?<!,)) # Empty value
+ )? # last item in a list - or string value
+ )|
+ (,) # alternatively a single comma - empty list
+ )
+ \s*(\#.*)? # optional comment
+ $''',
+ re.VERBOSE)
+
+ # use findall to get the members of a list value
+ _listvalueexp = re.compile(r'''
+ (
+ (?:".*?")| # double quotes
+ (?:'.*?')| # single quotes
+ (?:[^'",\#].*?) # unquoted
+ )
+ \s*,\s* # comma
+ ''',
+ re.VERBOSE)
+
+ # this regexp is used for the value
+ # when lists are switched off
+ _nolistvalue = re.compile(r'''^
+ (
+ (?:".*?")| # double quotes
+ (?:'.*?')| # single quotes
+ (?:[^'"\#].*?)| # unquoted
+ (?:) # Empty value
+ )
+ \s*(\#.*)? # optional comment
+ $''',
+ re.VERBOSE)
+
+ # regexes for finding triple quoted values on one line
+ _single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$")
+ _single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$')
+ _multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$")
+ _multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$')
+
+ _triple_quote = {
+ "'''": (_single_line_single, _multi_line_single),
+ '"""': (_single_line_double, _multi_line_double),
+ }
+
+ # Used by the ``istrue`` Section method
+ _bools = {
+ 'yes': True, 'no': False,
+ 'on': True, 'off': False,
+ '1': True, '0': False,
+ 'true': True, 'false': False,
+ }
+
+
+ def __init__(self, infile=None, options=None, _inspec=False, **kwargs):
+ """
+ Parse a config file or create a config file object.
+
+ ``ConfigObj(infile=None, options=None, **kwargs)``
+ """
+ self._inspec = _inspec
+ # init the superclass
+ Section.__init__(self, self, 0, self)
+
+ infile = infile or []
+ options = dict(options or {})
+
+ # keyword arguments take precedence over an options dictionary
+ options.update(kwargs)
+ if _inspec:
+ options['list_values'] = False
+
+ defaults = OPTION_DEFAULTS.copy()
+ # TODO: check the values too.
+ for entry in options:
+ if entry not in defaults:
+ raise TypeError('Unrecognised option "%s".' % entry)
+
+ # Add any explicit options to the defaults
+ defaults.update(options)
+ self._initialise(defaults)
+ configspec = defaults['configspec']
+ self._original_configspec = configspec
+ self._load(infile, configspec)
+
+
+ def _load(self, infile, configspec):
+ if isinstance(infile, StringTypes):
+ self.filename = infile
+ if os.path.isfile(infile):
+ h = open(infile, 'rb')
+ infile = h.read() or []
+ h.close()
+ elif self.file_error:
+ # raise an error if the file doesn't exist
+ raise IOError('Config file not found: "%s".' % self.filename)
+ else:
+ # file doesn't already exist
+ if self.create_empty:
+ # this is a good test that the filename specified
+ # isn't impossible - like on a non-existent device
+ h = open(infile, 'w')
+ h.write('')
+ h.close()
+ infile = []
+
+ elif isinstance(infile, (list, tuple)):
+ infile = list(infile)
+
+ elif isinstance(infile, dict):
+ # initialise self
+ # the Section class handles creating subsections
+ if isinstance(infile, ConfigObj):
+ # get a copy of our ConfigObj
+ infile = infile.dict()
+
+ for entry in infile:
+ self[entry] = infile[entry]
+ del self._errors
+
+ if configspec is not None:
+ self._handle_configspec(configspec)
+ else:
+ self.configspec = None
+ return
+
+ elif hasattr(infile, 'read'):
+ # This supports file like objects
+ infile = infile.read() or []
+ # needs splitting into lines - but needs doing *after* decoding
+ # in case it's not an 8 bit encoding
+ else:
+ raise TypeError('infile must be a filename, file like object, or list of lines.')
+
+ if infile:
+ # don't do it for the empty ConfigObj
+ infile = self._handle_bom(infile)
+ # infile is now *always* a list
+ #
+ # Set the newlines attribute (first line ending it finds)
+ # and strip trailing '\n' or '\r' from lines
+ for line in infile:
+ if (not line) or (line[-1] not in ('\r', '\n', '\r\n')):
+ continue
+ for end in ('\r\n', '\n', '\r'):
+ if line.endswith(end):
+ self.newlines = end
+ break
+ break
+
+ infile = [line.rstrip('\r\n') for line in infile]
+
+ self._parse(infile)
+ # if we had any errors, now is the time to raise them
+ if self._errors:
+ info = "at line %s." % self._errors[0].line_number
+ if len(self._errors) > 1:
+ msg = "Parsing failed with several errors.\nFirst error %s" % info
+ error = ConfigObjError(msg)
+ else:
+ error = self._errors[0]
+ # set the errors attribute; it's a list of tuples:
+ # (error_type, message, line_number)
+ error.errors = self._errors
+ # set the config attribute
+ error.config = self
+ raise error
+ # delete private attributes
+ del self._errors
+
+ if configspec is None:
+ self.configspec = None
+ else:
+ self._handle_configspec(configspec)
+
+
+ def _initialise(self, options=None):
+ if options is None:
+ options = OPTION_DEFAULTS
+
+ # initialise a few variables
+ self.filename = None
+ self._errors = []
+ self.raise_errors = options['raise_errors']
+ self.interpolation = options['interpolation']
+ self.list_values = options['list_values']
+ self.create_empty = options['create_empty']
+ self.file_error = options['file_error']
+ self.stringify = options['stringify']
+ self.indent_type = options['indent_type']
+ self.encoding = options['encoding']
+ self.default_encoding = options['default_encoding']
+ self.BOM = False
+ self.newlines = None
+ self.write_empty_values = options['write_empty_values']
+ self.unrepr = options['unrepr']
+
+ self.initial_comment = []
+ self.final_comment = []
+ self.configspec = None
+
+ if self._inspec:
+ self.list_values = False
+
+ # Clear section attributes as well
+ Section._initialise(self)
+
+
+ def __repr__(self):
+ return ('ConfigObj({%s})' %
+ ', '.join([('%s: %s' % (repr(key), repr(self[key])))
+ for key in (self.scalars + self.sections)]))
+
+
+ def _handle_bom(self, infile):
+ """
+ Handle any BOM, and decode if necessary.
+
+ If an encoding is specified, that *must* be used - but the BOM should
+ still be removed (and the BOM attribute set).
+
+ (If the encoding is wrongly specified, then a BOM for an alternative
+ encoding won't be discovered or removed.)
+
+ If an encoding is not specified, UTF8 or UTF16 BOM will be detected and
+ removed. The BOM attribute will be set. UTF16 will be decoded to
+ unicode.
+
+ NOTE: This method must not be called with an empty ``infile``.
+
+ Specifying the *wrong* encoding is likely to cause a
+ ``UnicodeDecodeError``.
+
+ ``infile`` must always be returned as a list of lines, but may be
+ passed in as a single string.
+ """
+ if ((self.encoding is not None) and
+ (self.encoding.lower() not in BOM_LIST)):
+ # No need to check for a BOM
+ # the encoding specified doesn't have one
+ # just decode
+ return self._decode(infile, self.encoding)
+
+ if isinstance(infile, (list, tuple)):
+ line = infile[0]
+ else:
+ line = infile
+ if self.encoding is not None:
+ # encoding explicitly supplied
+ # And it could have an associated BOM
+ # TODO: if encoding is just UTF16 - we ought to check for both
+ # TODO: big endian and little endian versions.
+ enc = BOM_LIST[self.encoding.lower()]
+ if enc == 'utf_16':
+ # For UTF16 we try big endian and little endian
+ for BOM, (encoding, final_encoding) in BOMS.items():
+ if not final_encoding:
+ # skip UTF8
+ continue
+ if infile.startswith(BOM):
+ ### BOM discovered
+ ##self.BOM = True
+ # Don't need to remove BOM
+ return self._decode(infile, encoding)
+
+ # If we get this far, will *probably* raise a DecodeError
+ # As it doesn't appear to start with a BOM
+ return self._decode(infile, self.encoding)
+
+ # Must be UTF8
+ BOM = BOM_SET[enc]
+ if not line.startswith(BOM):
+ return self._decode(infile, self.encoding)
+
+ newline = line[len(BOM):]
+
+ # BOM removed
+ if isinstance(infile, (list, tuple)):
+ infile[0] = newline
+ else:
+ infile = newline
+ self.BOM = True
+ return self._decode(infile, self.encoding)
+
+ # No encoding specified - so we need to check for UTF8/UTF16
+ for BOM, (encoding, final_encoding) in BOMS.items():
+ if not line.startswith(BOM):
+ continue
+ else:
+ # BOM discovered
+ self.encoding = final_encoding
+ if not final_encoding:
+ self.BOM = True
+ # UTF8
+ # remove BOM
+ newline = line[len(BOM):]
+ if isinstance(infile, (list, tuple)):
+ infile[0] = newline
+ else:
+ infile = newline
+ # UTF8 - don't decode
+ if isinstance(infile, StringTypes):
+ return infile.splitlines(True)
+ else:
+ return infile
+ # UTF16 - have to decode
+ return self._decode(infile, encoding)
+
+ # No BOM discovered and no encoding specified, just return
+ if isinstance(infile, StringTypes):
+ # infile read from a file will be a single string
+ return infile.splitlines(True)
+ return infile
+
+
+ def _a_to_u(self, aString):
+ """Decode ASCII strings to unicode if a self.encoding is specified."""
+ if self.encoding:
+ return aString.decode('ascii')
+ else:
+ return aString
+
+
+ def _decode(self, infile, encoding):
+ """
+ Decode infile to unicode. Using the specified encoding.
+
+ if is a string, it also needs converting to a list.
+ """
+ if isinstance(infile, StringTypes):
+ # can't be unicode
+ # NOTE: Could raise a ``UnicodeDecodeError``
+ return infile.decode(encoding).splitlines(True)
+ for i, line in enumerate(infile):
+ if not isinstance(line, unicode):
+ # NOTE: The isinstance test here handles mixed lists of unicode/string
+ # NOTE: But the decode will break on any non-string values
+ # NOTE: Or could raise a ``UnicodeDecodeError``
+ infile[i] = line.decode(encoding)
+ return infile
+
+
+ def _decode_element(self, line):
+ """Decode element to unicode if necessary."""
+ if not self.encoding:
+ return line
+ if isinstance(line, str) and self.default_encoding:
+ return line.decode(self.default_encoding)
+ return line
+
+
+ def _str(self, value):
+ """
+ Used by ``stringify`` within validate, to turn non-string values
+ into strings.
+ """
+ if not isinstance(value, StringTypes):
+ return str(value)
+ else:
+ return value
+
+
+ def _parse(self, infile):
+ """Actually parse the config file."""
+ temp_list_values = self.list_values
+ if self.unrepr:
+ self.list_values = False
+
+ comment_list = []
+ done_start = False
+ this_section = self
+ maxline = len(infile) - 1
+ cur_index = -1
+ reset_comment = False
+
+ while cur_index < maxline:
+ if reset_comment:
+ comment_list = []
+ cur_index += 1
+ line = infile[cur_index]
+ sline = line.strip()
+ # do we have anything on the line ?
+ if not sline or sline.startswith('#'):
+ reset_comment = False
+ comment_list.append(line)
+ continue
+
+ if not done_start:
+ # preserve initial comment
+ self.initial_comment = comment_list
+ comment_list = []
+ done_start = True
+
+ reset_comment = True
+ # first we check if it's a section marker
+ mat = self._sectionmarker.match(line)
+ if mat is not None:
+ # is a section line
+ (indent, sect_open, sect_name, sect_close, comment) = mat.groups()
+ if indent and (self.indent_type is None):
+ self.indent_type = indent
+ cur_depth = sect_open.count('[')
+ if cur_depth != sect_close.count(']'):
+ self._handle_error("Cannot compute the section depth at line %s.",
+ NestingError, infile, cur_index)
+ continue
+
+ if cur_depth < this_section.depth:
+ # the new section is dropping back to a previous level
+ try:
+ parent = self._match_depth(this_section,
+ cur_depth).parent
+ except SyntaxError:
+ self._handle_error("Cannot compute nesting level at line %s.",
+ NestingError, infile, cur_index)
+ continue
+ elif cur_depth == this_section.depth:
+ # the new section is a sibling of the current section
+ parent = this_section.parent
+ elif cur_depth == this_section.depth + 1:
+ # the new section is a child the current section
+ parent = this_section
+ else:
+ self._handle_error("Section too nested at line %s.",
+ NestingError, infile, cur_index)
+
+ sect_name = self._unquote(sect_name)
+ if parent.has_key(sect_name):
+ self._handle_error('Duplicate section name at line %s.',
+ DuplicateError, infile, cur_index)
+ continue
+
+ # create the new section
+ this_section = Section(
+ parent,
+ cur_depth,
+ self,
+ name=sect_name)
+ parent[sect_name] = this_section
+ parent.inline_comments[sect_name] = comment
+ parent.comments[sect_name] = comment_list
+ continue
+ #
+ # it's not a section marker,
+ # so it should be a valid ``key = value`` line
+ mat = self._keyword.match(line)
+ if mat is None:
+ # it neither matched as a keyword
+ # or a section marker
+ self._handle_error(
+ 'Invalid line at line "%s".',
+ ParseError, infile, cur_index)
+ else:
+ # is a keyword value
+ # value will include any inline comment
+ (indent, key, value) = mat.groups()
+ if indent and (self.indent_type is None):
+ self.indent_type = indent
+ # check for a multiline value
+ if value[:3] in ['"""', "'''"]:
+ try:
+ (value, comment, cur_index) = self._multiline(
+ value, infile, cur_index, maxline)
+ except SyntaxError:
+ self._handle_error(
+ 'Parse error in value at line %s.',
+ ParseError, infile, cur_index)
+ continue
+ else:
+ if self.unrepr:
+ comment = ''
+ try:
+ value = unrepr(value)
+ except Exception, e:
+ if type(e) == UnknownType:
+ msg = 'Unknown name or type in value at line %s.'
+ else:
+ msg = 'Parse error in value at line %s.'
+ self._handle_error(msg, UnreprError, infile,
+ cur_index)
+ continue
+ else:
+ if self.unrepr:
+ comment = ''
+ try:
+ value = unrepr(value)
+ except Exception, e:
+ if isinstance(e, UnknownType):
+ msg = 'Unknown name or type in value at line %s.'
+ else:
+ msg = 'Parse error in value at line %s.'
+ self._handle_error(msg, UnreprError, infile,
+ cur_index)
+ continue
+ else:
+ # extract comment and lists
+ try:
+ (value, comment) = self._handle_value(value)
+ except SyntaxError:
+ self._handle_error(
+ 'Parse error in value at line %s.',
+ ParseError, infile, cur_index)
+ continue
+ #
+ key = self._unquote(key)
+ if this_section.has_key(key):
+ self._handle_error(
+ 'Duplicate keyword name at line %s.',
+ DuplicateError, infile, cur_index)
+ continue
+ # add the key.
+ # we set unrepr because if we have got this far we will never
+ # be creating a new section
+ this_section.__setitem__(key, value, unrepr=True)
+ this_section.inline_comments[key] = comment
+ this_section.comments[key] = comment_list
+ continue
+ #
+ if self.indent_type is None:
+ # no indentation used, set the type accordingly
+ self.indent_type = ''
+
+ # preserve the final comment
+ if not self and not self.initial_comment:
+ self.initial_comment = comment_list
+ elif not reset_comment:
+ self.final_comment = comment_list
+ self.list_values = temp_list_values
+
+
+ def _match_depth(self, sect, depth):
+ """
+ Given a section and a depth level, walk back through the sections
+ parents to see if the depth level matches a previous section.
+
+ Return a reference to the right section,
+ or raise a SyntaxError.
+ """
+ while depth < sect.depth:
+ if sect is sect.parent:
+ # we've reached the top level already
+ raise SyntaxError()
+ sect = sect.parent
+ if sect.depth == depth:
+ return sect
+ # shouldn't get here
+ raise SyntaxError()
+
+
+ def _handle_error(self, text, ErrorClass, infile, cur_index):
+ """
+ Handle an error according to the error settings.
+
+ Either raise the error or store it.
+ The error will have occured at ``cur_index``
+ """
+ line = infile[cur_index]
+ cur_index += 1
+ message = text % cur_index
+ error = ErrorClass(message, cur_index, line)
+ if self.raise_errors:
+ # raise the error - parsing stops here
+ raise error
+ # store the error
+ # reraise when parsing has finished
+ self._errors.append(error)
+
+
+ def _unquote(self, value):
+ """Return an unquoted version of a value"""
+ if (value[0] == value[-1]) and (value[0] in ('"', "'")):
+ value = value[1:-1]
+ return value
+
+
+ def _quote(self, value, multiline=True):
+ """
+ Return a safely quoted version of a value.
+
+ Raise a ConfigObjError if the value cannot be safely quoted.
+ If multiline is ``True`` (default) then use triple quotes
+ if necessary.
+
+ * Don't quote values that don't need it.
+ * Recursively quote members of a list and return a comma joined list.
+ * Multiline is ``False`` for lists.
+ * Obey list syntax for empty and single member lists.
+
+ If ``list_values=False`` then the value is only quoted if it contains
+ a ``\\n`` (is multiline) or '#'.
+
+ If ``write_empty_values`` is set, and the value is an empty string, it
+ won't be quoted.
+ """
+ if multiline and self.write_empty_values and value == '':
+ # Only if multiline is set, so that it is used for values not
+ # keys, and not values that are part of a list
+ return ''
+
+ if multiline and isinstance(value, (list, tuple)):
+ if not value:
+ return ','
+ elif len(value) == 1:
+ return self._quote(value[0], multiline=False) + ','
+ return ', '.join([self._quote(val, multiline=False)
+ for val in value])
+ if not isinstance(value, StringTypes):
+ if self.stringify:
+ value = str(value)
+ else:
+ raise TypeError('Value "%s" is not a string.' % value)
+
+ if not value:
+ return '""'
+
+ no_lists_no_quotes = not self.list_values and '\n' not in value and '#' not in value
+ need_triple = multiline and ((("'" in value) and ('"' in value)) or ('\n' in value ))
+ hash_triple_quote = multiline and not need_triple and ("'" in value) and ('"' in value) and ('#' in value)
+ check_for_single = (no_lists_no_quotes or not need_triple) and not hash_triple_quote
+
+ if check_for_single:
+ if not self.list_values:
+ # we don't quote if ``list_values=False``
+ quot = noquot
+ # for normal values either single or double quotes will do
+ elif '\n' in value:
+ # will only happen if multiline is off - e.g. '\n' in key
+ raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
+ elif ((value[0] not in wspace_plus) and
+ (value[-1] not in wspace_plus) and
+ (',' not in value)):
+ quot = noquot
+ else:
+ quot = self._get_single_quote(value)
+ else:
+ # if value has '\n' or "'" *and* '"', it will need triple quotes
+ quot = self._get_triple_quote(value)
+
+ if quot == noquot and '#' in value and self.list_values:
+ quot = self._get_single_quote(value)
+
+ return quot % value
+
+
+ def _get_single_quote(self, value):
+ if ("'" in value) and ('"' in value):
+ raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
+ elif '"' in value:
+ quot = squot
+ else:
+ quot = dquot
+ return quot
+
+
+ def _get_triple_quote(self, value):
+ if (value.find('"""') != -1) and (value.find("'''") != -1):
+ raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
+ if value.find('"""') == -1:
+ quot = tdquot
+ else:
+ quot = tsquot
+ return quot
+
+
+ def _handle_value(self, value):
+ """
+ Given a value string, unquote, remove comment,
+ handle lists. (including empty and single member lists)
+ """
+ if self._inspec:
+ # Parsing a configspec so don't handle comments
+ return (value, '')
+ # do we look for lists in values ?
+ if not self.list_values:
+ mat = self._nolistvalue.match(value)
+ if mat is None:
+ raise SyntaxError()
+ # NOTE: we don't unquote here
+ return mat.groups()
+ #
+ mat = self._valueexp.match(value)
+ if mat is None:
+ # the value is badly constructed, probably badly quoted,
+ # or an invalid list
+ raise SyntaxError()
+ (list_values, single, empty_list, comment) = mat.groups()
+ if (list_values == '') and (single is None):
+ # change this if you want to accept empty values
+ raise SyntaxError()
+ # NOTE: note there is no error handling from here if the regex
+ # is wrong: then incorrect values will slip through
+ if empty_list is not None:
+ # the single comma - meaning an empty list
+ return ([], comment)
+ if single is not None:
+ # handle empty values
+ if list_values and not single:
+ # FIXME: the '' is a workaround because our regex now matches
+ # '' at the end of a list if it has a trailing comma
+ single = None
+ else:
+ single = single or '""'
+ single = self._unquote(single)
+ if list_values == '':
+ # not a list value
+ return (single, comment)
+ the_list = self._listvalueexp.findall(list_values)
+ the_list = [self._unquote(val) for val in the_list]
+ if single is not None:
+ the_list += [single]
+ return (the_list, comment)
+
+
+ def _multiline(self, value, infile, cur_index, maxline):
+ """Extract the value, where we are in a multiline situation."""
+ quot = value[:3]
+ newvalue = value[3:]
+ single_line = self._triple_quote[quot][0]
+ multi_line = self._triple_quote[quot][1]
+ mat = single_line.match(value)
+ if mat is not None:
+ retval = list(mat.groups())
+ retval.append(cur_index)
+ return retval
+ elif newvalue.find(quot) != -1:
+ # somehow the triple quote is missing
+ raise SyntaxError()
+ #
+ while cur_index < maxline:
+ cur_index += 1
+ newvalue += '\n'
+ line = infile[cur_index]
+ if line.find(quot) == -1:
+ newvalue += line
+ else:
+ # end of multiline, process it
+ break
+ else:
+ # we've got to the end of the config, oops...
+ raise SyntaxError()
+ mat = multi_line.match(line)
+ if mat is None:
+ # a badly formed line
+ raise SyntaxError()
+ (value, comment) = mat.groups()
+ return (newvalue + value, comment, cur_index)
+
+
+ def _handle_configspec(self, configspec):
+ """Parse the configspec."""
+ # FIXME: Should we check that the configspec was created with the
+ # correct settings ? (i.e. ``list_values=False``)
+ if not isinstance(configspec, ConfigObj):
+ try:
+ configspec = ConfigObj(configspec,
+ raise_errors=True,
+ file_error=True,
+ _inspec=True)
+ except ConfigObjError, e:
+ # FIXME: Should these errors have a reference
+ # to the already parsed ConfigObj ?
+ raise ConfigspecError('Parsing configspec failed: %s' % e)
+ except IOError, e:
+ raise IOError('Reading configspec failed: %s' % e)
+
+ self.configspec = configspec
+
+
+
+ def _set_configspec(self, section, copy):
+ """
+ Called by validate. Handles setting the configspec on subsections
+ including sections to be validated by __many__
+ """
+ configspec = section.configspec
+ many = configspec.get('__many__')
+ if isinstance(many, dict):
+ for entry in section.sections:
+ if entry not in configspec:
+ section[entry].configspec = many
+
+ for entry in configspec.sections:
+ if entry == '__many__':
+ continue
+ if entry not in section:
+ section[entry] = {}
+ if copy:
+ # copy comments
+ section.comments[entry] = configspec.comments.get(entry, [])
+ section.inline_comments[entry] = configspec.inline_comments.get(entry, '')
+
+ # Could be a scalar when we expect a section
+ if isinstance(section[entry], Section):
+ section[entry].configspec = configspec[entry]
+
+
+ def _write_line(self, indent_string, entry, this_entry, comment):
+ """Write an individual line, for the write method"""
+ # NOTE: the calls to self._quote here handles non-StringType values.
+ if not self.unrepr:
+ val = self._decode_element(self._quote(this_entry))
+ else:
+ val = repr(this_entry)
+ return '%s%s%s%s%s' % (indent_string,
+ self._decode_element(self._quote(entry, multiline=False)),
+ self._a_to_u(' = '),
+ val,
+ self._decode_element(comment))
+
+
+ def _write_marker(self, indent_string, depth, entry, comment):
+ """Write a section marker line"""
+ return '%s%s%s%s%s' % (indent_string,
+ self._a_to_u('[' * depth),
+ self._quote(self._decode_element(entry), multiline=False),
+ self._a_to_u(']' * depth),
+ self._decode_element(comment))
+
+
+ def _handle_comment(self, comment):
+ """Deal with a comment."""
+ if not comment:
+ return ''
+ start = self.indent_type
+ if not comment.startswith('#'):
+ start += self._a_to_u(' # ')
+ return (start + comment)
+
+
+ # Public methods
+
+ def write(self, outfile=None, section=None):
+ """
+ Write the current ConfigObj as a file
+
+ tekNico: FIXME: use StringIO instead of real files
+
+ >>> filename = a.filename
+ >>> a.filename = 'test.ini'
+ >>> a.write()
+ >>> a.filename = filename
+ >>> a == ConfigObj('test.ini', raise_errors=True)
+ 1
+ """
+ if self.indent_type is None:
+ # this can be true if initialised from a dictionary
+ self.indent_type = DEFAULT_INDENT_TYPE
+
+ out = []
+ cs = self._a_to_u('#')
+ csp = self._a_to_u('# ')
+ if section is None:
+ int_val = self.interpolation
+ self.interpolation = False
+ section = self
+ for line in self.initial_comment:
+ line = self._decode_element(line)
+ stripped_line = line.strip()
+ if stripped_line and not stripped_line.startswith(cs):
+ line = csp + line
+ out.append(line)
+
+ indent_string = self.indent_type * section.depth
+ for entry in (section.scalars + section.sections):
+ if entry in section.defaults:
+ # don't write out default values
+ continue
+ for comment_line in section.comments[entry]:
+ comment_line = self._decode_element(comment_line.lstrip())
+ if comment_line and not comment_line.startswith(cs):
+ comment_line = csp + comment_line
+ out.append(indent_string + comment_line)
+ this_entry = section[entry]
+ comment = self._handle_comment(section.inline_comments[entry])
+
+ if isinstance(this_entry, dict):
+ # a section
+ out.append(self._write_marker(
+ indent_string,
+ this_entry.depth,
+ entry,
+ comment))
+ out.extend(self.write(section=this_entry))
+ else:
+ out.append(self._write_line(
+ indent_string,
+ entry,
+ this_entry,
+ comment))
+
+ if section is self:
+ for line in self.final_comment:
+ line = self._decode_element(line)
+ stripped_line = line.strip()
+ if stripped_line and not stripped_line.startswith(cs):
+ line = csp + line
+ out.append(line)
+ self.interpolation = int_val
+
+ if section is not self:
+ return out
+
+ if (self.filename is None) and (outfile is None):
+ # output a list of lines
+ # might need to encode
+ # NOTE: This will *screw* UTF16, each line will start with the BOM
+ if self.encoding:
+ out = [l.encode(self.encoding) for l in out]
+ if (self.BOM and ((self.encoding is None) or
+ (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
+ # Add the UTF8 BOM
+ if not out:
+ out.append('')
+ out[0] = BOM_UTF8 + out[0]
+ return out
+
+ # Turn the list to a string, joined with correct newlines
+ newline = self.newlines or os.linesep
+ output = self._a_to_u(newline).join(out)
+ if self.encoding:
+ output = output.encode(self.encoding)
+ if self.BOM and ((self.encoding is None) or match_utf8(self.encoding)):
+ # Add the UTF8 BOM
+ output = BOM_UTF8 + output
+
+ if not output.endswith(newline):
+ output += newline
+ if outfile is not None:
+ outfile.write(output)
+ else:
+ h = open(self.filename, 'wb')
+ h.write(output)
+ h.close()
+
+
+ def validate(self, validator, preserve_errors=False, copy=False,
+ section=None):
+ """
+ Test the ConfigObj against a configspec.
+
+ It uses the ``validator`` object from *validate.py*.
+
+ To run ``validate`` on the current ConfigObj, call: ::
+
+ test = config.validate(validator)
+
+ (Normally having previously passed in the configspec when the ConfigObj
+ was created - you can dynamically assign a dictionary of checks to the
+ ``configspec`` attribute of a section though).
+
+ It returns ``True`` if everything passes, or a dictionary of
+ pass/fails (True/False). If every member of a subsection passes, it
+ will just have the value ``True``. (It also returns ``False`` if all
+ members fail).
+
+ In addition, it converts the values from strings to their native
+ types if their checks pass (and ``stringify`` is set).
+
+ If ``preserve_errors`` is ``True`` (``False`` is default) then instead
+ of a marking a fail with a ``False``, it will preserve the actual
+ exception object. This can contain info about the reason for failure.
+ For example the ``VdtValueTooSmallError`` indicates that the value
+ supplied was too small. If a value (or section) is missing it will
+ still be marked as ``False``.
+
+ You must have the validate module to use ``preserve_errors=True``.
+
+ You can then use the ``flatten_errors`` function to turn your nested
+ results dictionary into a flattened list of failures - useful for
+ displaying meaningful error messages.
+ """
+ if section is None:
+ if self.configspec is None:
+ raise ValueError('No configspec supplied.')
+ if preserve_errors:
+ # We do this once to remove a top level dependency on the validate module
+ # Which makes importing configobj faster
+ from validate import VdtMissingValue
+ self._vdtMissingValue = VdtMissingValue
+
+ section = self
+
+ if copy:
+ section.initial_comment = section.configspec.initial_comment
+ section.final_comment = section.configspec.final_comment
+ section.encoding = section.configspec.encoding
+ section.BOM = section.configspec.BOM
+ section.newlines = section.configspec.newlines
+ section.indent_type = section.configspec.indent_type
+
+ #
+ configspec = section.configspec
+ self._set_configspec(section, copy)
+
+ def validate_entry(entry, spec, val, missing, ret_true, ret_false):
+ try:
+ check = validator.check(spec,
+ val,
+ missing=missing
+ )
+ except validator.baseErrorClass, e:
+ if not preserve_errors or isinstance(e, self._vdtMissingValue):
+ out[entry] = False
+ else:
+ # preserve the error
+ out[entry] = e
+ ret_false = False
+ ret_true = False
+ else:
+ try:
+ section.default_values.pop(entry, None)
+ except AttributeError:
+ # For Python 2.2 compatibility
+ try:
+ del section.default_values[entry]
+ except KeyError:
+ pass
+
+ try:
+ section.default_values[entry] = validator.get_default_value(configspec[entry])
+ except (KeyError, AttributeError):
+ # No default or validator has no 'get_default_value' (e.g. SimpleVal)
+ pass
+
+ ret_false = False
+ out[entry] = True
+ if self.stringify or missing:
+ # if we are doing type conversion
+ # or the value is a supplied default
+ if not self.stringify:
+ if isinstance(check, (list, tuple)):
+ # preserve lists
+ check = [self._str(item) for item in check]
+ elif missing and check is None:
+ # convert the None from a default to a ''
+ check = ''
+ else:
+ check = self._str(check)
+ if (check != val) or missing:
+ section[entry] = check
+ if not copy and missing and entry not in section.defaults:
+ section.defaults.append(entry)
+ return ret_true, ret_false
+
+ #
+ out = {}
+ ret_true = True
+ ret_false = True
+
+ unvalidated = [k for k in section.scalars if k not in configspec]
+ incorrect_sections = [k for k in configspec.sections if k in section.scalars]
+ incorrect_scalars = [k for k in configspec.scalars if k in section.sections]
+
+ for entry in configspec.scalars:
+ if entry in ('__many__', '___many___'):
+ # reserved names
+ continue
+
+ if (not entry in section.scalars) or (entry in section.defaults):
+ # missing entries
+ # or entries from defaults
+ missing = True
+ val = None
+ if copy and not entry in section.scalars:
+ # copy comments
+ section.comments[entry] = (
+ configspec.comments.get(entry, []))
+ section.inline_comments[entry] = (
+ configspec.inline_comments.get(entry, ''))
+ #
+ else:
+ missing = False
+ val = section[entry]
+
+ ret_true, ret_false = validate_entry(entry, configspec[entry], val,
+ missing, ret_true, ret_false)
+
+ many = None
+ if '__many__' in configspec.scalars:
+ many = configspec['__many__']
+ elif '___many___' in configspec.scalars:
+ many = configspec['___many___']
+
+ if many is not None:
+ for entry in unvalidated:
+ val = section[entry]
+ ret_true, ret_false = validate_entry(entry, many, val, False,
+ ret_true, ret_false)
+
+ for entry in incorrect_scalars:
+ ret_true = False
+ if not preserve_errors:
+ out[entry] = False
+ else:
+ ret_false = False
+ msg = 'Value %r was provided as a section' % entry
+ out[entry] = validator.baseErrorClass(msg)
+ for entry in incorrect_sections:
+ ret_true = False
+ if not preserve_errors:
+ out[entry] = False
+ else:
+ ret_false = False
+ msg = 'Section %r was provided as a single value' % entry
+ out[entry] = validator.baseErrorClass(msg)
+
+ # Missing sections will have been created as empty ones when the
+ # configspec was read.
+ for entry in section.sections:
+ # FIXME: this means DEFAULT is not copied in copy mode
+ if section is self and entry == 'DEFAULT':
+ continue
+ if section[entry].configspec is None:
+ continue
+ if copy:
+ section.comments[entry] = configspec.comments.get(entry, [])
+ section.inline_comments[entry] = configspec.inline_comments.get(entry, '')
+ check = self.validate(validator, preserve_errors=preserve_errors, copy=copy, section=section[entry])
+ out[entry] = check
+ if check == False:
+ ret_true = False
+ elif check == True:
+ ret_false = False
+ else:
+ ret_true = False
+ ret_false = False
+ #
+ if ret_true:
+ return True
+ elif ret_false:
+ return False
+ return out
+
+
+ def reset(self):
+ """Clear ConfigObj instance and restore to 'freshly created' state."""
+ self.clear()
+ self._initialise()
+ # FIXME: Should be done by '_initialise', but ConfigObj constructor (and reload)
+ # requires an empty dictionary
+ self.configspec = None
+ # Just to be sure ;-)
+ self._original_configspec = None
+
+
+ def reload(self):
+ """
+ Reload a ConfigObj from file.
+
+ This method raises a ``ReloadError`` if the ConfigObj doesn't have
+ a filename attribute pointing to a file.
+ """
+ if not isinstance(self.filename, StringTypes):
+ raise ReloadError()
+
+ filename = self.filename
+ current_options = {}
+ for entry in OPTION_DEFAULTS:
+ if entry == 'configspec':
+ continue
+ current_options[entry] = getattr(self, entry)
+
+ configspec = self._original_configspec
+ current_options['configspec'] = configspec
+
+ self.clear()
+ self._initialise(current_options)
+ self._load(filename, configspec)
+
+
+
+class SimpleVal(object):
+ """
+ A simple validator.
+ Can be used to check that all members expected are present.
+
+ To use it, provide a configspec with all your members in (the value given
+ will be ignored). Pass an instance of ``SimpleVal`` to the ``validate``
+ method of your ``ConfigObj``. ``validate`` will return ``True`` if all
+ members are present, or a dictionary with True/False meaning
+ present/missing. (Whole missing sections will be replaced with ``False``)
+ """
+
+ def __init__(self):
+ self.baseErrorClass = ConfigObjError
+
+ def check(self, check, member, missing=False):
+ """A dummy check method, always returns the value unchanged."""
+ if missing:
+ raise self.baseErrorClass()
+ return member
+
+
+# Check / processing functions for options
+def flatten_errors(cfg, res, levels=None, results=None):
+ """
+ An example function that will turn a nested dictionary of results
+ (as returned by ``ConfigObj.validate``) into a flat list.
+
+ ``cfg`` is the ConfigObj instance being checked, ``res`` is the results
+ dictionary returned by ``validate``.
+
+ (This is a recursive function, so you shouldn't use the ``levels`` or
+ ``results`` arguments - they are used by the function.)
+
+ Returns a list of keys that failed. Each member of the list is a tuple :
+
+ ::
+
+ ([list of sections...], key, result)
+
+ If ``validate`` was called with ``preserve_errors=False`` (the default)
+ then ``result`` will always be ``False``.
+
+ *list of sections* is a flattened list of sections that the key was found
+ in.
+
+ If the section was missing (or a section was expected and a scalar provided
+ - or vice-versa) then key will be ``None``.
+
+ If the value (or section) was missing then ``result`` will be ``False``.
+
+ If ``validate`` was called with ``preserve_errors=True`` and a value
+ was present, but failed the check, then ``result`` will be the exception
+ object returned. You can use this as a string that describes the failure.
+
+ For example *The value "3" is of the wrong type*.
+
+ >>> import validate
+ >>> vtor = validate.Validator()
+ >>> my_ini = '''
+ ... option1 = True
+ ... [section1]
+ ... option1 = True
+ ... [section2]
+ ... another_option = Probably
+ ... [section3]
+ ... another_option = True
+ ... [[section3b]]
+ ... value = 3
+ ... value2 = a
+ ... value3 = 11
+ ... '''
+ >>> my_cfg = '''
+ ... option1 = boolean()
+ ... option2 = boolean()
+ ... option3 = boolean(default=Bad_value)
+ ... [section1]
+ ... option1 = boolean()
+ ... option2 = boolean()
+ ... option3 = boolean(default=Bad_value)
+ ... [section2]
+ ... another_option = boolean()
+ ... [section3]
+ ... another_option = boolean()
+ ... [[section3b]]
+ ... value = integer
+ ... value2 = integer
+ ... value3 = integer(0, 10)
+ ... [[[section3b-sub]]]
+ ... value = string
+ ... [section4]
+ ... another_option = boolean()
+ ... '''
+ >>> cs = my_cfg.split('\\n')
+ >>> ini = my_ini.split('\\n')
+ >>> cfg = ConfigObj(ini, configspec=cs)
+ >>> res = cfg.validate(vtor, preserve_errors=True)
+ >>> errors = []
+ >>> for entry in flatten_errors(cfg, res):
+ ... section_list, key, error = entry
+ ... section_list.insert(0, '[root]')
+ ... if key is not None:
+ ... section_list.append(key)
+ ... else:
+ ... section_list.append('[missing]')
+ ... section_string = ', '.join(section_list)
+ ... errors.append((section_string, ' = ', error))
+ >>> errors.sort()
+ >>> for entry in errors:
+ ... print entry[0], entry[1], (entry[2] or 0)
+ [root], option2 = 0
+ [root], option3 = the value "Bad_value" is of the wrong type.
+ [root], section1, option2 = 0
+ [root], section1, option3 = the value "Bad_value" is of the wrong type.
+ [root], section2, another_option = the value "Probably" is of the wrong type.
+ [root], section3, section3b, section3b-sub, [missing] = 0
+ [root], section3, section3b, value2 = the value "a" is of the wrong type.
+ [root], section3, section3b, value3 = the value "11" is too big.
+ [root], section4, [missing] = 0
+ """
+ if levels is None:
+ # first time called
+ levels = []
+ results = []
+ if res is True:
+ return results
+ if res is False or isinstance(res, Exception):
+ results.append((levels[:], None, res))
+ if levels:
+ levels.pop()
+ return results
+ for (key, val) in res.items():
+ if val == True:
+ continue
+ if isinstance(cfg.get(key), dict):
+ # Go down one level
+ levels.append(key)
+ flatten_errors(cfg[key], val, levels, results)
+ continue
+ results.append((levels[:], key, val))
+ #
+ # Go up one level
+ if levels:
+ levels.pop()
+ #
+ return results
+
+
+"""*A programming language is a medium of expression.* - Paul Graham"""
diff --git a/docs/configobj.txt b/docs/configobj.txt
new file mode 100644
index 0000000..cb0c9a8
--- /dev/null
+++ b/docs/configobj.txt
@@ -0,0 +1,2863 @@
+==================================
+ Reading and Writing Config Files
+==================================
+
+----------------------------------------
+ ConfigObj 4 Introduction and Reference
+----------------------------------------
+
+:Authors: Michael Foord, Nicola Larosa
+:Version: ConfigObj 4.5.3
+:Date: 2008/06/27
+:Homepage: `ConfigObj Homepage`_
+:Sourceforge: Sourceforge_
+:Development: `SVN Repository`_
+:License: `BSD License`_
+:Support: `Mailing List`_
+
+.. _Mailing List: http://lists.sourceforge.net/lists/listinfo/configobj-develop
+.. _SVN Repository: http://svn.pythonutils.python-hosting.com
+
+.. meta::
+ :description: ConfigObj - a Python module for easy reading and writing of
+ config files.
+ :keywords: python, script, module, config, configuration, data, persistence,
+ developer, configparser
+
+
+.. contents:: ConfigObj Manual
+.. sectnum::
+
+
+Introduction
+============
+
+**ConfigObj** is a simple but powerful config file reader and writer: an *ini
+file round tripper*. Its main feature is that it is very easy to use, with a
+straightforward programmer's interface and a simple syntax for config files.
+It has lots of other features though :
+
+* Nested sections (subsections), to any level
+* List values
+* Multiple line values
+* String interpolation (substitution)
+* Integrated with a powerful validation system
+
+ - including automatic type checking/conversion
+ - repeated sections
+ - and allowing default values
+
+* When writing out config files, ConfigObj preserves all comments and the order of members and sections
+* Many useful methods and options for working with configuration files (like the 'reload' method)
+* Full Unicode support
+
+
+For support and bug reports please use the ConfigObj `Mailing List`_.
+
+
+Downloading
+===========
+
+The current version is **4.5.3**, dated 27th June 2008. ConfigObj 4 is
+stable and mature. We still expect to pick up a few bugs along the way though [#]_.
+{sm;:-)}
+
+You can get ConfigObj in the following ways :
+
+Files
+-----
+
+* configobj.py_ from Voidspace
+
+ ConfigObj has no external dependencies. This file is sufficient to access
+ all the functionality except Validation_.
+
+* configobj.zip_ from Voidspace
+
+ This also contains validate.py_ and `this document`_.
+
+* The latest development version can be obtained from the `Subversion
+ Repository`_.
+
+* validate.py_ from Voidspace
+
+* You can also download *configobj.zip* from Sourceforge_
+
+Documentation
+-------------
+
+*configobj.zip* also contains `this document`_.
+
+* You can view `this document`_ online at the `ConfigObj Homepage`_.
+
+
+Pythonutils
+-----------
+
+ConfigObj is also part of the Pythonutils_ set of modules. This contains
+various other useful modules, and is required by many of the `Voidspace Python
+Projects`_.
+
+
+Development Version
+-------------------
+
+It is sometimes possible to get the latest *development version* of ConfigObj
+from the `Subversion Repository <http://svn.pythonutils.python-hosting.com/trunk/pythonutils/>`_.
+
+.. _configobj.py: http://www.voidspace.org.uk/cgi-bin/voidspace/downman.py?file=configobj.py
+.. _configobj.zip: http://www.voidspace.org.uk/cgi-bin/voidspace/downman.py?file=configobj-4.5.3.zip
+.. _validate.py: http://www.voidspace.org.uk/cgi-bin/voidspace/downman.py?file=validate.py
+.. _this document:
+.. _configobj homepage: http://www.voidspace.org.uk/python/configobj.html
+.. _Sourceforge: http://sourceforge.net/projects/configobj
+.. _pythonutils: http://www.voidspace.org.uk/python/pythonutils.html
+.. _Voidspace Python Projects: http://www.voidspace.org.uk/python/index.shtml
+
+
+
+ConfigObj in the Real World
+===========================
+
+**ConfigObj** is widely used. Projects using it include:
+
+* `Bazaar <http://bazaar-ng.org>`_.
+
+ Bazaar is a Python distributed {acro;VCS;Version Control System}.
+ ConfigObj is used to read ``bazaar.conf`` and ``branches.conf``.
+
+* `Turbogears <http://www.turbogears.org/>`_
+
+ Turbogears is a web application framework.
+
+* `Chandler <http://chandler.osafoundation.org/>`_
+
+ A Python and `wxPython <http://www.wxpython.org>`_
+ {acro;PIM;Personal Information Manager}, being developed by the
+ `OSAFoundation <http://www.osafoundation.org/>`_.
+
+* `matplotlib <http://matplotlib.sourceforge.net/>`_
+
+ A 2D plotting library.
+
+* `IPython <http://ipython.scipy.org/moin/>`_
+
+ IPython is an enhanced interactive Python shell. IPython uses ConfigObj in a module called 'TConfig' that combines it with enthought `Traits <http://code.enthought.com/traits/>`_: `tconfig <http://ipython.scipy.org/ipython/ipython/browser/ipython/branches/saw/sandbox/tconfig>`_.
+
+* `Elisa - the Fluendo Mediacenter <http://elisa.fluendo.com/>`_
+
+ Elisa is an open source cross-platform media center solution designed to be simple for people not particularly familiar with computers.
+
+
+Getting Started
+===============
+
+The outstanding feature of using ConfigObj is simplicity. Most functions can be
+performed with single line commands.
+
+
+Reading a Config File
+---------------------
+
+The normal way to read a config file, is to give ConfigObj the filename :
+
+.. raw:: html
+
+ {+coloring}
+
+ from configobj import ConfigObj
+ config = ConfigObj(filename)
+
+ {-coloring}
+
+You can also pass the config file in as a list of lines, or a ``StringIO``
+instance, so it doesn't matter where your config data comes from.
+
+You can then access members of your config file as a dictionary. Subsections
+will also be dictionaries.
+
+.. raw:: html
+
+ {+coloring}
+
+ from configobj import ConfigObj
+ config = ConfigObj(filename)
+ #
+ value1 = config['keyword1']
+ value2 = config['keyword2']
+ #
+ section1 = config['section1']
+ value3 = section1['keyword3']
+ value4 = section1['keyword4']
+ #
+ # you could also write
+ value3 = config['section1']['keyword3']
+ value4 = config['section1']['keyword4']
+
+ {-coloring}
+
+
+Writing a Config File
+---------------------
+
+Creating a new config file is just as easy as reading one. You can specify a
+filename when you create the ConfigObj, or do it later [#]_.
+
+If you *don't* set a filename, then the ``write`` method will return a list of
+lines instead of writing to file. See the write_ method for more details.
+
+Here we show creating an empty ConfigObj, setting a filename and some values,
+and then writing to file :
+
+.. raw:: html
+
+ {+coloring}
+
+ from configobj import ConfigObj
+ config = ConfigObj()
+ config.filename = filename
+ #
+ config['keyword1'] = value1
+ config['keyword2'] = value2
+ #
+ config['section1'] = {}
+ config['section1']['keyword3'] = value3
+ config['section1']['keyword4'] = value4
+ #
+ section2 = {
+ 'keyword5': value5,
+ 'keyword6': value6,
+ 'sub-section': {
+ 'keyword7': value7
+ }
+ }
+ config['section2'] = section2
+ #
+ config['section3'] = {}
+ config['section3']['keyword 8'] = [value8, value9, value10]
+ config['section3']['keyword 9'] = [value11, value12, value13]
+ #
+ config.write()
+
+ {-coloring}
+
+.. caution::
+
+ Keywords and section names can only be strings [#]_. Attempting to set
+ anything else will raise a ``ValueError``.
+
+
+Config Files
+------------
+
+The config files that ConfigObj will read and write are based on the 'INI'
+format. This means it will read and write files created for ``ConfigParser``
+[#]_.
+
+Keywords and values are separated by an ``'='``, and section markers are
+between square brackets. Keywords, values, and section names can be surrounded
+by single or double quotes. Indentation is not significant, but can be
+preserved.
+
+Subsections are indicated by repeating the square brackets in the section
+marker. You nest levels by using more brackets.
+
+You can have list values by separating items with a comma, and values spanning
+multiple lines by using triple quotes (single or double).
+
+For full details on all these see `the config file format`_. Here's an example
+to illustrate : ::
+
+ # This is the 'initial_comment'
+ # Which may be several lines
+ keyword1 = value1
+ 'keyword 2' = 'value 2'
+
+ [ "section 1" ]
+ # This comment goes with keyword 3
+ keyword 3 = value 3
+ 'keyword 4' = value4, value 5, 'value 6'
+
+ [[ sub-section ]] # an inline comment
+ # sub-section is inside "section 1"
+ 'keyword 5' = 'value 7'
+ 'keyword 6' = '''A multiline value,
+ that spans more than one line :-)
+ The line breaks are included in the value.'''
+
+ [[[ sub-sub-section ]]]
+ # sub-sub-section is *in* 'sub-section'
+ # which is in 'section 1'
+ 'keyword 7' = 'value 8'
+
+ [section 2] # an inline comment
+ keyword8 = "value 9"
+ keyword9 = value10 # an inline comment
+ # The 'final_comment'
+ # Which also may be several lines
+
+
+ConfigObj specifications
+========================
+
+.. raw:: html
+
+ {+coloring}
+
+ config = ConfigObj(infile=None, options=None, **keywargs)
+
+ {-coloring}
+
+
+infile
+------
+
+You don't need to specify an infile. If you omit it, an empty ConfigObj will be
+created. ``infile`` *can* be :
+
+* Nothing. In which case the ``filename`` attribute of your ConfigObj will be
+ ``None``. You can set a filename at any time.
+
+* A filename. What happens if the file doesn't already exist is determined by
+ the options_ ``file_error`` and ``create_empty``. The filename will be
+ preserved as the ``filename`` attribute. This can be changed at any time.
+
+* A list of lines. Any trailing newlines will be removed from the lines. The
+ ``filename`` attribute of your ConfigObj will be ``None``.
+
+* A ``StringIO`` instance or file object, or any object with a ``read`` method.
+ The ``filename`` attribute of your ConfigObj will be ``None`` [#]_.
+
+* A dictionary. You can initialise a ConfigObj from a dictionary [#]_. The
+ ``filename`` attribute of your ConfigObj will be ``None``. All keys must be
+ strings. In this case, the order of values and sections is arbitrary.
+
+
+options
+-------
+
+There are various options that control the way ConfigObj behaves. They can be
+passed in as a dictionary of options, or as keyword arguments. Explicit keyword
+arguments override the dictionary.
+
+All of the options are available as attributes after the config file has been
+parsed.
+
+ConfigObj has the following options (with the default values shown) :
+
+* 'raise_errors': ``False``
+
+ When parsing, it is possible that the config file will be badly formed. The
+ default is to parse the whole file and raise a single error at the end. You
+ can set ``raise_errors = True`` to have errors raised immediately. See the
+ exceptions_ section for more details.
+
+ Altering this value after initial parsing has no effect.
+
+* 'list_values': ``True``
+
+ If ``True`` (the default) then list values are possible. If ``False``, the
+ values are not parsed for lists.
+
+ If ``list_values = False`` then single line values are not quoted or
+ unquoted when reading and writing.
+
+ Changing this value affects whether single line values will be quoted or
+ not when writing.
+
+* 'create_empty': ``False``
+
+ If this value is ``True`` and the file specified by ``infile`` doesn't
+ exist, ConfigObj will create an empty file. This can be a useful test that
+ the filename makes sense: an impossible filename will cause an error.
+
+ Altering this value after initial parsing has no effect.
+
+* 'file_error': ``False``
+
+ If this value is ``True`` and the file specified by ``infile`` doesn't
+ exist, ConfigObj will raise an ``IOError``.
+
+ Altering this value after initial parsing has no effect.
+
+* 'interpolation': ``True``
+
+ Whether string interpolation is switched on or not. It is on (``True``) by
+ default.
+
+ You can set this attribute to change whether string interpolation is done
+ when values are fetched. See the `String Interpolation`_ section for more details.
+
+* 'configspec': ``None``
+
+ If you want to use the validation system, you supply a configspec. This is
+ effectively a type of config file that specifies a check for each member.
+ This check can be used to do type conversion as well as check that the
+ value is within your required parameters.
+
+ You provide a configspec in the same way as you do the initial file: a
+ filename, or list of lines, etc. See the validation_ section for full
+ details on how to use the system.
+
+ When parsed, every section has a ``configspec`` with a dictionary of
+ configspec checks for *that section*.
+
+* 'stringify': ``True``
+
+ If you use the validation scheme, it can do type checking *and* conversion
+ for you. This means you may want to set members to integers, or other
+ non-string values.
+
+ If 'stringify' is set to ``True`` (default) then non-string values will
+ be converted to strings when you write the config file. The validation_
+ process converts values from strings to the required type.
+
+ If 'stringify' is set to ``False``, attempting to set a member to a
+ non-string value [#]_ will raise a ``TypeError`` (no type conversion is
+ done by validation).
+
+* 'indent_type': ``' '``
+
+ Indentation is not significant; it can however be present in the input and
+ output config. Any combination of tabs and spaces may be used: the string
+ will be repeated for each level of indentation. Typical values are: ``''``
+ (no indentation), ``' '`` (indentation with four spaces, the default),
+ ``'\t'`` (indentation with one tab).
+
+ If this option is not specified, and the ConfigObj is initialised with a
+ dictionary, the indentation used in the output is the default one, that is,
+ four spaces.
+
+ If this option is not specified, and the ConfigObj is initialised with a
+ list of lines or a file, the indentation used in the first indented line is
+ selected and used in all output lines. If no input line is indented, no
+ output line will be either.
+
+ If this option *is* specified, the option value is used in the output
+ config, overriding the type of indentation in the input config (if any).
+
+* 'encoding': ``None``
+
+ By default **ConfigObj** does not decode the file/strings you pass it into
+ Unicode [#]_. If you want your config file as Unicode (keys and members)
+ you need to provide an encoding to decode the file with. This encoding will
+ also be used to encode the config file when writing.
+
+ You can change the encoding attribute at any time.
+
+ Any characters in your strings that can't be encoded with the specified
+ encoding will raise a ``UnicodeEncodeError``.
+
+ .. note::
+
+ ``UTF16`` encoded files will automatically be detected and decoded,
+ even if ``encoding`` is ``None``.
+
+ This is because it is a 16-bit encoding, and ConfigObj will mangle it
+ (split characters on byte boundaries) if it parses it without decoding.
+
+* 'default_encoding': ``None``
+
+ When using the ``write`` method, **ConfigObj** uses the ``encoding``
+ attribute to encode the Unicode strings. If any members (or keys) have
+ been set as byte strings instead of Unicode, these must first be decoded
+ to Unicode before outputting in the specified encoding.
+
+ ``default_encoding``, if specified, is the encoding used to decode byte
+ strings in the **ConfigObj** before writing. If this is ``None``, then
+ the Python default encoding (``sys.defaultencoding`` - usually ASCII) is
+ used.
+
+ For most Western European users, a value of ``latin-1`` is sensible.
+
+ ``default_encoding`` is *only* used if an ``encoding`` is specified.
+
+ Any characters in byte-strings that can't be decoded using the
+ ``default_encoding`` will raise a ``UnicodeDecodeError``.
+
+* 'unrepr': ``False``
+
+ The ``unrepr`` option reads and writes files in a different mode. This
+ allows you to store and retrieve the basic Python data-types using config
+ files.
+
+ This uses Python syntax for lists and quoting. See `unrepr mode`_ for the
+ full details.
+
+* 'write_empty_values': ``False``
+
+ If ``write_empty_values`` is ``True``, empty strings are written as
+ empty values. See `Empty Values`_ for more details.
+
+
+Methods
+-------
+
+The ConfigObj is a subclass of an object called ``Section``, which is itself a
+subclass of ``dict``, the builtin dictionary type. This means it also has
+**all** the normal dictionary methods.
+
+In addition, the following `Section Methods`_ may be useful :
+
+* 'restore_default'
+* 'restore_defaults'
+* 'walk'
+* 'merge'
+* 'dict'
+* 'as_bool'
+* 'as_float'
+* 'as_int'
+
+Read about Sections_ for details of all the methods.
+
+.. hint::
+
+ The *merge* method of sections is a recursive update.
+
+ You can use this to merge sections, or even whole ConfigObjs, into each
+ other.
+
+ You would typically use this to create a default ConfigObj and then merge
+ in user settings. This way users only need to specify values that are
+ different from the default. You can use configspecs and validation to
+ achieve the same thing of course.
+
+
+The public methods available on ConfigObj are :
+
+* 'write'
+* 'validate'
+* 'reset'
+* 'reload'
+
+
+write
+~~~~~
+
+::
+
+ write(file_object=None)
+
+This method writes the current ConfigObj and takes a single, optional argument
+[#]_.
+
+If you pass in a file like object to the ``write`` method, the config file will
+be written to this. (The only method of this object that is used is its
+``write`` method, so a ``StringIO`` instance, or any other file like object
+will work.)
+
+Otherwise, the behaviour of this method depends on the ``filename`` attribute
+of the ConfigObj.
+
+``filename``
+ ConfigObj will write the configuration to the file specified.
+
+``None``
+ ``write`` returns a list of lines. (Not ``'\n'`` terminated)
+
+First the 'initial_comment' is written, then the config file, followed by the
+'final_comment'. Comment lines and inline comments are written with each
+key/value.
+
+
+validate
+~~~~~~~~
+
+::
+
+ validate(validator, preserve_errors=False, copy=False)
+
+.. raw:: html
+
+ {+coloring}
+
+ # filename is the config file
+ # filename2 is the configspec
+ # (which could also be hardcoded into your program)
+ config = ConfigObj(filename, configspec=filename2)
+ #
+ from validate import Validator
+ val = Validator()
+ test = config.validate(val)
+ if test == True:
+ print 'Succeeded.'
+ {-coloring}
+
+The validate method uses the `validate
+<http://www.voidspace.org.uk/python/validate.html>`__ module to do the
+validation.
+
+This method validates the ConfigObj against the configspec. By doing type
+conversion as well it can abstract away the config file altogether and present
+the config *data* to your application (in the types it expects it to be).
+
+If the ``configspec`` attribute of the ConfigObj is ``None``, it raises a
+``ValueError``.
+
+If the stringify_ attribute is set, this process will convert values to the
+type defined in the configspec.
+
+The validate method uses checks specified in the configspec and defined in the
+``Validator`` object. It is very easy to extend.
+
+The configspec looks like the config file, but instead of the value, you
+specify the check (and any default value). See the validation_ section for
+details.
+
+.. hint::
+
+ The system of configspecs can seem confusing at first, but is actually
+ quite simple and powerful. For a concrete example of how to use it, you may
+ find this blog entry helpful :
+ `Transforming Values with ConfigObj <http://www.voidspace.org.uk/python/weblog/arch_d7_2006_03_04.shtml#e257>`_.
+
+
+The ``copy`` parameter fills in missing values from the configspec (default
+values), *without* marking the values as defaults. It also causes comments to
+be copied from the configspec into the config file. This allows you to use a
+configspec to create default config files. (Normally default values aren't
+written out by the ``write`` method.)
+
+As of ConfigObj 4.3.0 you can also pass in a ConfigObj instance as your
+configspec. This is especially useful if you need to specify the encoding of
+your configspec file. When you read your configspec file, you *must* specify
+``list_values=False``.
+
+.. raw:: html
+
+ {+coloring}
+ from configobj import ConfigObj
+ configspec = ConfigObj(configspecfilename, encoding='UTF8',
+ list_values=False)
+ config = ConfigObj(filename, configspec=configspec)
+ {-coloring}
+
+
+Return Value
+############
+
+By default, the validate method either returns ``True`` (everything passed)
+or a dictionary of ``True``/``False`` representing pass/fail. The dictionary
+follows the structure of the ConfigObj.
+
+If a whole section passes then it is replaced with the value ``True``. If a
+whole section fails, then it is replaced with the value ``False``.
+
+If a value is missing, and there is no default in the check, then the check
+automatically fails.
+
+The ``validate`` method takes an optional keyword argument ``preserve_errors``.
+If you set this to ``True``, instead of getting ``False`` for failed checks you
+get the actual error object from the **validate** module. This usually contains
+useful information about why the check failed.
+
+See the `flatten_errors`_ function for how to turn your results dictionary into
+a useful list of error messages.
+
+Even if ``preserve_errors`` is ``True``, missing keys or sections will still be
+represented by a ``False`` in the results dictionary.
+
+
+Mentioning Default Values
+#########################
+
+In the check in your configspec, you can specify a default to be used - by
+using the ``default`` keyword. E.g. ::
+
+ key1 = integer(0, 30, default=15)
+ key2 = integer(default=15)
+ key3 = boolean(default=True)
+ key4 = option('Hello', 'Goodbye', 'Not Today', default='Not Today')
+
+If the configspec check supplies a default and the value is missing in the
+config, then the default will be set in your ConfigObj. (It is still passed to
+the ``Validator`` so that type conversion can be done: this means the default
+value must still pass the check.)
+
+ConfigObj keeps a record of which values come from defaults, using the
+``defaults`` attribute of sections_. Any key in this list isn't written out by
+the ``write`` method. If a key is set from outside (even to the same value)
+then it is removed from the ``defaults`` list.
+
+.. note:
+
+ Even if all the keys in a section are in the defaults list, the section
+ marker is still written out.
+
+There is additionally a special case default value of ``None``. If you set the
+default value to ``None`` and the value is missing, the value will always be
+set to ``None``. As the other checks don't return ``None`` (unless you
+implement your own that do), you can tell that this value came from a default
+value (and was missing from the config file). It allows an easy way of
+implementing optional values. Simply check (and ignore) members that are set
+to ``None``.
+
+.. note::
+
+ If stringify_ is ``False`` then ``default=None`` returns ``''`` instead of
+ ``None``. This is because setting a value to a non-string raises an error
+ if stringify is unset.
+
+The default value can be a list. See `List Values`_ for the way to do this.
+
+Writing invalid default values is a *guaranteed* way of confusing your users.
+Default values **must** pass the check.
+
+
+Mentioning Repeated Sections
+############################
+
+In the configspec it is possible to cause *every* sub-section in a section to
+be validated using the same configspec. You do this with a section in the
+configspec called ``__many__``. Every sub-section in that section has the
+``__many__`` configspec applied to it (without you having to explicitly name
+them in advance).
+
+If you define a ``__many__`` type section it must the only sub-section in that
+section. Having a ``__many__`` *and* other sub-sections defined in the same
+section will raise a ``RepeatSectionError``.
+
+Your ``__many__`` section can have nested subsections, which can also include
+``__many__`` type sections.
+
+See `Repeated Sections`_ for examples.
+
+
+Mentioning SimpleVal
+####################
+
+If you just want to check if all members are present, then you can use the
+``SimpleVal`` object that comes with ConfigObj. It only fails members if they
+are missing.
+
+Write a configspec that has all the members you want to check for, but set
+every section to ``''``.
+
+.. raw:: html
+
+ {+coloring}
+
+ val = SimpleVal()
+ test = config.validate(val)
+ if test is True:
+ print 'Succeeded.'
+
+ {-coloring}
+
+
+Mentioning copy Mode
+####################
+
+As discussed in `Mentioning Default Values`_, you can use a configspec to
+supply default values. These are marked in the ConfigObj instance as defaults,
+and *not* written out by the ``write`` mode. This means that your users only
+need to supply values that are different from the defaults.
+
+This can be inconvenient if you *do* want to write out the default values,
+for example to write out a default config file.
+
+If you set ``copy=True`` when you call validate, then no values are marked as
+defaults. In addition, all comments from the configspec are copied into
+your ConfigObj instance. You can then call ``write`` to create your config
+file.
+
+There is a limitation with this. In order to allow `String Interpolation`_ to work
+within configspecs, ``DEFAULT`` sections are not processed by
+validation; even in copy mode.
+
+
+reload
+~~~~~~
+
+If a ConfigObj instance was loaded from the filesystem, then this method will reload it. It
+will also reuse any configspec you supplied at instantiation (including reloading it from
+the filesystem if you passed it in as a filename).
+
+If the ConfigObj does not have a filename attribute pointing to a file, then a ``ReloadError``
+will be raised.
+
+
+reset
+~~~~~
+
+This method takes no arguments and doesn't return anything. It restores a ConfigObj
+instance to a freshly created state.
+
+
+Attributes
+----------
+
+A ConfigObj has the following attributes :
+
+* indent_type
+* interpolate
+* stringify
+* BOM
+* initial_comment
+* final_comment
+* list_values
+* encoding
+* default_encoding
+* unrepr
+* write_empty_values
+* newlines
+
+.. note::
+
+ This doesn't include *comments*, *inline_comments*, *defaults*, or
+ *configspec*. These are actually attributes of Sections_.
+
+It also has the following attributes as a result of parsing. They correspond to
+options_ when the ConfigObj was created, but changing them has no effect.
+
+* raise_errors
+* create_empty
+* file_error
+
+
+interpolation
+~~~~~~~~~~~~~
+
+ConfigObj can perform string interpolation in a *similar* way to
+``ConfigParser``. See the `String Interpolation`_ section for full details.
+
+If ``interpolation`` is set to ``False``, then interpolation is *not* done when
+you fetch values.
+
+
+stringify
+~~~~~~~~~
+
+If this attribute is set (``True``) then the validate_ method changes the
+values in the ConfigObj. These are turned back into strings when write_ is
+called.
+
+If stringify is unset (``False``) then attempting to set a value to a non
+string (or a list of strings) will raise a ``TypeError``.
+
+
+BOM
+~~~
+
+If the initial config file *started* with the UTF8 Unicode signature (known
+slightly incorrectly as the {acro;BOM;Byte Order Mark}), or the UTF16 BOM, then
+this attribute is set to ``True``. Otherwise it is ``False``.
+
+If it is set to ``True`` when ``write`` is called then, if ``encoding`` is set
+to ``None`` *or* to ``utf_8`` (and variants) a UTF BOM will be written.
+
+For UTF16 encodings, a BOM is *always* written.
+
+
+initial_comment
+~~~~~~~~~~~~~~~
+
+This is a list of lines. If the ConfigObj is created from an existing file, it
+will contain any lines of comments before the start of the members.
+
+If you create a new ConfigObj, this will be an empty list.
+
+The write method puts these lines before it starts writing out the members.
+
+
+final_comment
+~~~~~~~~~~~~~
+
+This is a list of lines. If the ConfigObj is created from an existing file, it
+will contain any lines of comments after the last member.
+
+If you create a new ConfigObj, this will be an empty list.
+
+The ``write`` method puts these lines after it finishes writing out the
+members.
+
+
+list_values
+~~~~~~~~~~~
+
+This attribute is ``True`` or ``False``. If set to ``False`` then values are
+not parsed for list values. In addition single line values are not unquoted.
+
+This allows you to do your own parsing of values. It exists primarily to
+support the reading of the configspec_ - but has other use cases.
+
+For example you could use the ``LineParser`` from the
+`listquote module <http://www.voidspace.org.uk/python/listquote.html#lineparser>`_
+to read values for nested lists.
+
+Single line values aren't quoted when writing - but multiline values are
+handled as normal.
+
+.. caution::
+
+ Because values aren't quoted, leading or trailing whitespace can be
+ lost.
+
+ This behaviour was changed in version 4.0.1.
+
+ Prior to this, single line values might have been quoted; even with
+ ``list_values=False``. This means that files written by **ConfigObj**
+ *could* now be incompatible - and need the quotes removing by hand.
+
+
+encoding
+~~~~~~~~
+
+This is the encoding used to encode the output, when you call ``write``. It
+must be a valid encoding `recognised by Python <http://docs.python.org/lib/standard-encodings.html>`_.
+
+If this value is ``None`` then no encoding is done when ``write`` is called.
+
+
+default_encoding
+~~~~~~~~~~~~~~~~
+
+If encoding is set, any byte-strings in your ConfigObj instance (keys or
+members) will first be decoded to Unicode using the encoding specified by the
+``default_encoding`` attribute. This ensures that the output is in the encoding
+specified.
+
+If this value is ``None`` then ``sys.defaultencoding`` is used instead.
+
+
+unrepr
+~~~~~~
+
+Another boolean value. If this is set, then ``repr(value)`` is used to write
+values. This writes values in a slightly different way to the normal ConfigObj
+file syntax.
+
+This preserves basic Python data-types when read back in. See `unrepr mode`_
+for more details.
+
+
+write_empty_values
+~~~~~~~~~~~~~~~~~~
+
+Also boolean. If set, values that are an empty string (``''``) are written as
+empty values. See `Empty Values`_ for more details.
+
+
+newlines
+~~~~~~~~
+
+When a config file is read, ConfigObj records the type of newline separators in the
+file and uses this separator when writing. It defaults to ``None``, and ConfigObj
+uses the system default (``os.sep``) if write is called without newlines having
+been set.
+
+
+The Config File Format
+======================
+
+You saw an example config file in the `Config Files`_ section. Here is a fuller
+specification of the config files used and created by ConfigObj.
+
+The basic pattern for keywords is : ::
+
+ # comment line
+ # comment line
+ keyword = value # inline comment
+
+Both keyword and value can optionally be surrounded in quotes. The equals sign
+is the only valid divider.
+
+Values can have comments on the lines above them, and an inline comment after
+them. This, of course, is optional. See the comments_ section for details.
+
+If a keyword or value starts or ends with whitespace, or contains a quote mark
+or comma, then it should be surrounded by quotes. Quotes are not necessary if
+whitespace is surrounded by non-whitespace.
+
+Values can also be lists. Lists are comma separated. You indicate a single
+member list by a trailing comma. An empty list is shown by a single comma : ::
+
+ keyword1 = value1, value2, value3
+ keyword2 = value1, # a single member list
+ keyword3 = , # an empty list
+
+Values that contain line breaks (multi-line values) can be surrounded by triple
+quotes. These can also be used if a value contains both types of quotes. List
+members cannot be surrounded by triple quotes : ::
+
+ keyword1 = ''' A multi line value
+ on several
+ lines''' # with a comment
+ keyword2 = '''I won't be "afraid".'''
+ #
+ keyword3 = """ A multi line value
+ on several
+ lines""" # with a comment
+ keyword4 = """I won't be "afraid"."""
+
+.. warning::
+
+ There is no way of safely quoting values that contain both types of triple
+ quotes.
+
+A line that starts with a '#', possibly preceded by whitespace, is a comment.
+
+New sections are indicated by a section marker line. That is the section name
+in square brackets. Whitespace around the section name is ignored. The name can
+be quoted with single or double quotes. The marker can have comments before it
+and an inline comment after it : ::
+
+ # The First Section
+ [ section name 1 ] # first section
+ keyword1 = value1
+
+ # The Second Section
+ [ "section name 2" ] # second section
+ keyword2 = value2
+
+Any subsections (sections that are *inside* the current section) are
+designated by repeating the square brackets before and after the section name.
+The number of square brackets represents the nesting level of the sub-section.
+Square brackets may be separated by whitespace; such whitespace, however, will
+not be present in the output config written by the ``write`` method.
+
+Indentation is not significant, but can be preserved. See the description of
+the ``indent_type`` option, in the `ConfigObj specifications`_ chapter, for the
+details.
+
+A *NestingError* will be raised if the number of the opening and the closing
+brackets in a section marker is not the same, or if a sub-section's nesting
+level is greater than the nesting level of it parent plus one.
+
+In the outer section, single values can only appear before any sub-section.
+Otherwise they will belong to the sub-section immediately before them. ::
+
+ # initial comment
+ keyword1 = value1
+ keyword2 = value2
+
+ [section 1]
+ keyword1 = value1
+ keyword2 = value2
+
+ [[sub-section]]
+ # this is in section 1
+ keyword1 = value1
+ keyword2 = value2
+
+ [[[nested section]]]
+ # this is in sub section
+ keyword1 = value1
+ keyword2 = value2
+
+ [[sub-section2]]
+ # this is in section 1 again
+ keyword1 = value1
+ keyword2 = value2
+
+ [[sub-section3]]
+ # this is also in section 1, indentation is misleading here
+ keyword1 = value1
+ keyword2 = value2
+
+ # final comment
+
+When parsed, the above config file produces the following data structure :
+
+.. raw:: html
+
+ {+coloring}
+
+ ConfigObj({
+ 'keyword1': 'value1',
+ 'keyword2': 'value2',
+ 'section 1': {
+ 'keyword1': 'value1',
+ 'keyword2': 'value2',
+ 'sub-section': {
+ 'keyword1': 'value1',
+ 'keyword2': 'value2',
+ 'nested section': {
+ 'keyword1': 'value1',
+ 'keyword2': 'value2',
+ },
+ },
+ 'sub-section2': {
+ 'keyword1': 'value1',
+ 'keyword2': 'value2',
+ },
+ 'sub-section3': {
+ 'keyword1': 'value1',
+ 'keyword2': 'value2',
+ },
+ },
+ })
+
+ {-coloring}
+
+Sections are ordered: note how the structure of the resulting ConfigObj is in
+the same order as the original file.
+
+.. note::
+
+ In ConfigObj 4.3.0 *empty values* became valid syntax. They are read as the
+ empty string. There is also an option/attribute (``write_empty_values``) to
+ allow the writing of these.
+
+ This is mainly to support 'legacy' config files, written from other
+ applications. This is documented under `Empty Values`_.
+
+ `unrepr mode`_ introduces *another* syntax variation, used for storing
+ basic Python datatypes in config files. {sm;:-)}
+
+
+Sections
+========
+
+Every section in a ConfigObj has certain properties. The ConfigObj itself also
+has these properties, because it too is a section (sometimes called the *root
+section*).
+
+``Section`` is a subclass of the standard new-class dictionary, therefore it
+has **all** the methods of a normal dictionary. This means you can ``update``
+and ``clear`` sections.
+
+.. note::
+
+ You create a new section by assigning a member to be a dictionary.
+
+ The new ``Section`` is created *from* the dictionary, but isn't the same
+ thing as the dictionary. (So references to the dictionary you use to create
+ the section *aren't* references to the new section).
+
+ Note the following.
+
+ .. raw:: html
+
+ {+coloring}
+
+ config = ConfigObj()
+ vals = {'key1': 'value 1',
+ 'key2': 'value 2'
+ }
+ config['vals'] = vals
+ config['vals'] == vals
+ True
+ config['vals'] is vals
+ False
+
+ {-coloring}
+
+ If you now change ``vals``, the changes won't be reflected in ``config['vals']``.
+
+A section is ordered, following its ``scalars`` and ``sections``
+attributes documented below. This means that the following dictionary
+attributes return their results in order.
+
+* '__iter__'
+
+ More commonly known as ``for member in section:``.
+
+* '__repr__' and '__str__'
+
+ Any time you print or display the ConfigObj.
+
+* 'items'
+
+* 'iteritems'
+
+* 'iterkeys'
+
+* 'itervalues'
+
+* 'keys'
+
+* 'popitem'
+
+* 'values'
+
+
+Section Attributes
+------------------
+
+* main
+
+ A reference to the main ConfigObj.
+
+* parent
+
+ A reference to the 'parent' section, the section that this section is a
+ member of.
+
+ On the ConfigObj this attribute is a reference to itself. You can use this
+ to walk up the sections, stopping when ``section.parent is section``.
+
+* depth
+
+ The nesting level of the current section.
+
+ If you create a new ConfigObj and add sections, 1 will be added to the
+ depth level between sections.
+
+* defaults
+
+ This attribute is a list of scalars that came from default values. Values
+ that came from defaults aren't written out by the ``write`` method.
+ Setting any of these values in the section removes them from the defaults
+ list.
+
+* default_values
+
+ This attribute is a dictionary mapping keys to the default values for the
+ keys. By default it is an empty dictionary and is populated when you
+ validate the ConfigObj.
+
+* scalars, sections
+
+ These attributes are normal lists, representing the order that members,
+ single values and subsections appear in the section. The order will either
+ be the order of the original config file, *or* the order that you added
+ members.
+
+ The order of members in this lists is the order that ``write`` creates in
+ the config file. The ``scalars`` list is output before the ``sections``
+ list.
+
+ Adding or removing members also alters these lists. You can manipulate the
+ lists directly to alter the order of members.
+
+ .. warning::
+
+ If you alter the ``scalars``, ``sections``, or ``defaults`` attributes
+ so that they no longer reflect the contents of the section, you will
+ break your ConfigObj.
+
+ See also the ``rename`` method.
+
+* comments
+
+ This is a dictionary of comments associated with each member. Each entry is
+ a list of lines. These lines are written out before the member.
+
+* inline_comments
+
+ This is *another* dictionary of comments associated with each member. Each
+ entry is a string that is put inline with the member.
+
+* configspec
+
+ The configspec attribute is a dictionary mapping scalars to *checks*. A
+ check defines the expected type and possibly the allowed values for a
+ member.
+
+ The configspec has the same format as a config file, but instead of values
+ it has a specification for the value (which may include a default value).
+ The validate_ method uses it to check the config file makes sense. If a
+ configspec is passed in when the ConfigObj is created, then it is parsed
+ and broken up to become the ``configspec`` attribute of each section.
+
+ If you didn't pass in a configspec, this attribute will be ``None`` on the
+ root section (the main ConfigObj).
+
+ You can set the configspec attribute directly on a section.
+
+ See the validation_ section for full details of how to write configspecs.
+
+
+Section Methods
+---------------
+
+* **dict**
+
+ This method takes no arguments. It returns a deep copy of the section as a
+ dictionary. All subsections will also be dictionaries, and list values will
+ be copies, rather than references to the original [#]_.
+
+* **rename**
+
+ ``rename(oldkey, newkey)``
+
+ This method renames a key, without affecting its position in the sequence.
+
+ It is mainly implemented for the ``encode`` and ``decode`` methods, which
+ provide some Unicode support.
+
+* **merge**
+
+ ``merge(indict)``
+
+ This method is a *recursive update* method. It allows you to merge two
+ config files together.
+
+ You would typically use this to create a default ConfigObj and then merge
+ in user settings. This way users only need to specify values that are
+ different from the default.
+
+ For example :
+
+ .. raw:: html
+
+ {+coloring}
+
+ # def_cfg contains your default config settings
+ # user_cfg contains the user settings
+ cfg = ConfigObj(def_cfg)
+ usr = ConfigObj(user_cfg)
+ #
+ cfg.merge(usr)
+
+ """
+ cfg now contains a combination of the default settings and the user
+ settings.
+
+ The user settings will have overwritten any of the default ones.
+ """
+
+ {-coloring}
+
+* **walk**
+
+ This method can be used to transform values and names. See `walking a
+ section`_ for examples and explanation.
+
+* **decode**
+
+ ``decode(encoding)``
+
+ This method decodes names and values into Unicode objects, using the
+ supplied encoding.
+
+* **encode**
+
+ ``encode(encoding)``
+
+ This method is the opposite of ``decode`` {sm;:!:}.
+
+ It encodes names and values using the supplied encoding. If any of your
+ names/values are strings rather than Unicode, Python will have to do an
+ implicit decode first. (This method uses ``sys.defaultencoding`` for
+ implicit decodes.)
+
+* **as_bool**
+
+ ``as_bool(key)``
+
+ Returns ``True`` if the key contains a string that represents ``True``, or
+ is the ``True`` object.
+
+ Returns ``False`` if the key contains a string that represents ``False``,
+ or is the ``False`` object.
+
+ Raises a ``ValueError`` if the key contains anything else.
+
+ Strings that represent ``True`` are (not case sensitive) : ::
+
+ true, yes, on, 1
+
+ Strings that represent ``False`` are : ::
+
+ false, no, off, 0
+
+ .. note::
+
+ In ConfigObj 4.1.0, this method was called ``istrue``. That method is
+ now deprecated and will issue a warning when used. It will go away
+ in a future release.
+
+* **as_int**
+
+ ``as_int(key)``
+
+ This returns the value contained in the specified key as an integer.
+
+ It raises a ``ValueError`` if the conversion can't be done.
+
+* **as_float**
+
+ ``as_float(key)``
+
+ This returns the value contained in the specified key as a float.
+
+ It raises a ``ValueError`` if the conversion can't be done.
+
+* **restore_default**
+
+ ``restore_default(key)``
+
+ Restore (and return) the default value for the specified key.
+
+ This method will only work for a ConfigObj that was created
+ with a configspec and has been validated.
+
+ If there is no default value for this key, ``KeyError`` is raised.
+
+* **restore_defaults**
+
+ ``restore_defaults()``
+
+ Recursively restore default values to all members
+ that have them.
+
+ This method will only work for a ConfigObj that was created
+ with a configspec and has been validated.
+
+ It doesn't delete or modify entries without default values.
+
+
+Walking a Section
+-----------------
+
+.. note::
+
+ The walk method allows you to call a function on every member/name.
+
+.. raw:: html
+
+ {+coloring}
+
+ walk(function, raise_errors=True,
+ call_on_sections=False, **keywargs):
+
+ {-coloring}
+
+``walk`` is a method of the ``Section`` object. This means it is also a method
+of ConfigObj.
+
+It walks through every member and calls a function on the keyword and value. It
+walks recursively through subsections.
+
+It returns a dictionary of all the computed values.
+
+If the function raises an exception, the default is to propagate the error, and
+stop. If ``raise_errors=False`` then it sets the return value for that keyword
+to ``False`` instead, and continues. This is similar to the way validation_
+works.
+
+Your function receives the arguments ``(section, key)``. The current value is
+then ``section[key]`` [#]_. Any unrecognised keyword arguments you pass to
+walk, are passed on to the function.
+
+Normally ``walk`` just recurses into subsections. If you are transforming (or
+checking) names as well as values, then you want to be able to change the names
+of sections. In this case set ``call_on_sections`` to ``True``. Now, on
+encountering a sub-section, *first* the function is called for the *whole*
+sub-section, and *then* it recurses into it's members. This means your function
+must be able to handle receiving dictionaries as well as strings and lists.
+
+If you are using the return value from ``walk`` *and* ``call_on_sections``,
+note that walk discards the return value when it calls your function.
+
+.. caution::
+
+ You can use ``walk`` to transform the names of members of a section
+ but you mustn't add or delete members.
+
+
+Examples
+--------
+
+Examples that use the walk method are the ``encode`` and ``decode`` methods.
+They both define a function and pass it to walk. Because these functions
+transform names as well as values (from byte strings to Unicode) they set
+``call_on_sections=True``.
+
+To see how they do it, *read the source Luke* {sm;:cool:}.
+
+You can use this for transforming all values in your ConfigObj. For example
+you might like the nested lists from ConfigObj 3. This was provided by the
+listquote_ module. You could switch off the parsing for list values
+(``list_values=False``) and use listquote to parse every value.
+
+Another thing you might want to do is use the Python escape codes in your
+values. You might be *used* to using ``\n`` for line feed and ``\t`` for tab.
+Obviously we'd need to decode strings that come from the config file (using the
+escape codes). Before writing out we'll need to put the escape codes back in
+encode.
+
+As an example we'll write a function to use with walk, that encodes or decodes
+values using the ``string-escape`` codec.
+
+The function has to take each value and set the new value. As a bonus we'll
+create one function that will do decode *or* encode depending on a keyword
+argument.
+
+We don't want to work with section names, we're only transforming values, so
+we can leave ``call_on_sections`` as ``False``. This means the two datatypes we
+have to handle are strings and lists, we can ignore everything else. (We'll
+treat tuples as lists as well).
+
+We're not using the return values, so it doesn't need to return anything, just
+change the values if appropriate.
+
+.. raw:: html
+
+ {+coloring}
+
+ def string_escape(section, key, encode=False):
+ """
+ A function to encode or decode using the 'string-escape' codec.
+ To be passed to the walk method of a ConfigObj.
+ By default it decodes.
+ To encode, pass in the keyword argument ``encode=True``.
+ """
+ val = section[key]
+ # is it a type we can work with
+ # NOTE: for platforms where Python > 2.2
+ # you can use basestring instead of (str, unicode)
+ if not isinstance(val, (str, unicode, list, tuple)):
+ # no !
+ return
+ elif isinstance(val, (str, unicode)):
+ # it's a string !
+ if not encode:
+ section[key] = val.decode('string-escape')
+ else:
+ section[key] = val.encode('string-escape')
+ else:
+ # it must be a list or tuple!
+ # we'll be lazy and create a new list
+ newval = []
+ # we'll check every member of the list
+ for entry in val:
+ if isinstance(entry, (str, unicode)):
+ if not encode:
+ newval.append(entry.decode('string-escape'))
+ else:
+ newval.append(entry.encode('string-escape'))
+ else:
+ newval.append(entry)
+ # done !
+ section[key] = newval
+
+ # assume we have a ConfigObj called ``config``
+ #
+ # To decode
+ config.walk(string_escape)
+ #
+ # To encode.
+ # Because ``walk`` doesn't recognise the ``encode`` argument
+ # it passes it to our function.
+ config.walk(string_escape, encode=True)
+
+ {-coloring}
+
+Here's a simple example of using ``walk`` to transform names and values. One
+usecase of this would be to create a *standard* config file with placeholders
+for section and keynames. You can then use walk to create new config files
+and change values and member names :
+
+.. raw:: html
+
+ {+coloring}
+
+ # We use 'XXXX' as a placeholder
+ config = '''
+ XXXXkey1 = XXXXvalue1
+ XXXXkey2 = XXXXvalue2
+ XXXXkey3 = XXXXvalue3
+ [XXXXsection1]
+ XXXXkey1 = XXXXvalue1
+ XXXXkey2 = XXXXvalue2
+ XXXXkey3 = XXXXvalue3
+ [XXXXsection2]
+ XXXXkey1 = XXXXvalue1
+ XXXXkey2 = XXXXvalue2
+ XXXXkey3 = XXXXvalue3
+ [[XXXXsection1]]
+ XXXXkey1 = XXXXvalue1
+ XXXXkey2 = XXXXvalue2
+ XXXXkey3 = XXXXvalue3
+ '''.splitlines()
+ cfg = ConfigObj(config)
+ #
+ def transform(section, key):
+ val = section[key]
+ newkey = key.replace('XXXX', 'CLIENT1')
+ section.rename(key, newkey)
+ if isinstance(val, (tuple, list, dict)):
+ pass
+ else:
+ val = val.replace('XXXX', 'CLIENT1')
+ section[newkey] = val
+ #
+ cfg.walk(transform, call_on_sections=True)
+ print cfg
+ ConfigObj({'CLIENT1key1': 'CLIENT1value1', 'CLIENT1key2': 'CLIENT1value2',
+ 'CLIENT1key3': 'CLIENT1value3',
+ 'CLIENT1section1': {'CLIENT1key1': 'CLIENT1value1',
+ 'CLIENT1key2': 'CLIENT1value2', 'CLIENT1key3': 'CLIENT1value3'},
+ 'CLIENT1section2': {'CLIENT1key1': 'CLIENT1value1',
+ 'CLIENT1key2': 'CLIENT1value2', 'CLIENT1key3': 'CLIENT1value3',
+ 'CLIENT1section1': {'CLIENT1key1': 'CLIENT1value1',
+ 'CLIENT1key2': 'CLIENT1value2', 'CLIENT1key3': 'CLIENT1value3'}}})
+
+ {-coloring}
+
+
+Exceptions
+==========
+
+There are several places where ConfigObj may raise exceptions (other than
+because of bugs).
+
+1) If a configspec filename you pass in doesn't exist, or a config file
+ filename doesn't exist *and* ``file_error=True``, an ``IOError`` will be
+ raised.
+
+2) If you try to set a non-string key, or a non string value when
+ ``stringify=False``, a ``TypeError`` will be raised.
+
+3) A badly built config file will cause parsing errors.
+
+4) A parsing error can also occur when reading a configspec.
+
+5) In string interpolation you can specify a value that doesn't exist, or
+ create circular references (recursion).
+
+6) If you have a ``__many__`` repeated section with other section definitions
+ (in a configspec), a ``RepeatSectionError`` will be raised.
+
+Number 5 (which is actually two different types of exceptions) is documented
+ in `String Interpolation`_.
+
+Number 6 is explained in the validation_ section.
+
+*This* section is about errors raised during parsing.
+
+The base error class is ``ConfigObjError``. This is a subclass of
+``SyntaxError``, so you can trap for ``SyntaxError`` without needing to
+directly import any of the ConfigObj exceptions.
+
+The following other exceptions are defined (all deriving from
+``ConfigObjError``) :
+
+* ``NestingError``
+
+ This error indicates either a mismatch in the brackets in a section marker,
+ or an excessive level of nesting.
+
+* ``ParseError``
+
+ This error indicates that a line is badly written. It is neither a valid
+ ``key = value`` line, nor a valid section marker line, nor a comment line.
+
+* ``DuplicateError``
+
+ The keyword or section specified already exists.
+
+* ``ConfigspecError``
+
+ An error occurred whilst parsing a configspec.
+
+* ``UnreprError``
+
+ An error occurred when parsing a value in `unrepr mode`_.
+
+* ``ReloadError``
+
+ ``reload`` was called on a ConfigObj instance that doesn't have a valid
+ filename attribute.
+
+When parsing a configspec, ConfigObj will stop on the first error it
+encounters. It will raise a ``ConfigspecError``. This will have an ``error``
+attribute, which is the actual error that was raised.
+
+Behaviour when parsing a config file depends on the option ``raise_errors``.
+If ConfigObj encounters an error while parsing a config file:
+
+ If ``raise_errors=True`` then ConfigObj will raise the appropriate error
+ and parsing will stop.
+
+ If ``raise_errors=False`` (the default) then parsing will continue to the
+ end and *all* errors will be collected.
+
+If ``raise_errors`` is False and multiple errors are found a ``ConfigObjError``
+is raised. The error raised has a ``config`` attribute, which is the parts of
+the ConfigObj that parsed successfully. It also has an attribute ``errors``,
+which is a list of *all* the errors raised. Each entry in the list is an
+instance of the appropriate error type. Each one has the following attributes
+(useful for delivering a sensible error message to your user) :
+
+* ``line``: the original line that caused the error.
+
+* ``line_number``: its number in the config file.
+
+* ``message``: the error message that accompanied the error.
+
+If only one error is found, then that error is re-raised. The error still has
+the ``config`` and ``errors`` attributes. This means that your error handling
+code can be the same whether one error is raised in parsing , or several.
+
+It also means that in the most common case (a single error) a useful error
+message will be raised.
+
+.. note::
+
+ One wrongly written line could break the basic structure of your config
+ file. This could cause every line after it to flag an error, so having a
+ list of all the lines that caused errors may not be as useful as it sounds.
+ {sm;:-(}.
+
+
+Validation
+==========
+
+.. hint::
+
+ The system of configspecs can seem confusing at first, but is actually
+ quite simple and powerful. For a concrete example of how to use it, you may
+ find this blog entry helpful :
+ `Transforming Values with ConfigObj <http://www.voidspace.org.uk/python/weblog/arch_d7_2006_03_04.shtml#e257>`_.
+
+Validation is done through a combination of the configspec_ and a ``Validator``
+object. For this you need *validate.py* [#]_. See downloading_ if you don't
+have a copy.
+
+Validation can perform two different operations :
+
+1) Check that a value meets a specification. For example, check that a value
+ is an integer between one and six, or is a choice from a specific set of
+ options.
+
+2) It can convert the value into the type required. For example, if one of
+ your values is a port number, validation will turn it into an integer for
+ you.
+
+So validation can act as a transparent layer between the datatypes of your
+application configuration (boolean, integers, floats, etc) and the text format
+of your config file.
+
+
+configspec
+----------
+
+The ``validate`` method checks members against an entry in the configspec. Your
+configspec therefore resembles your config file, with a check for every member.
+
+In order to perform validation you need a ``Validator`` object. This has
+several useful built-in check functions. You can also create your own custom
+functions and register them with your Validator object.
+
+Each check is the name of one of these functions, including any parameters and
+keyword arguments. The configspecs look like function calls, and they map to
+function calls.
+
+The basic datatypes that an un-extended Validator can test for are :
+
+* boolean values (True and False)
+* integers (including minimum and maximum values)
+* floats (including min and max)
+* strings (including min and max length)
+* IP addresses (v4 only)
+
+It can also handle lists of these types and restrict a value to being one from
+a set of options.
+
+An example configspec is going to look something like : ::
+
+ port = integer(0, 100)
+ user = string(max=25)
+ mode = option('quiet', 'loud', 'silent')
+
+You can specify default values, and also have the same configspec applied to
+several sections. This is called `repeated sections`_.
+
+For full details on writing configspecs, please refer to the `validate.py
+documentation`_.
+
+.. important::
+
+ Your configspec is read by ConfigObj in the same way as a config file.
+
+ That means you can do interpolation *within* your configspec.
+
+ In order to allow this, checks in the 'DEFAULT' section (of the root level
+ of your configspec) are *not* used.
+
+If you need to specify the encoding of your configspec, then you can pass in a
+ConfigObj instance as your configspec. When you read your configspec file, you
+*must* specify ``list_values=False``.
+
+.. raw:: html
+
+ {+coloring}
+ from configobj import ConfigObj
+ configspec = ConfigObj(configspecfilename, encoding='UTF8',
+ list_values=False)
+ config = ConfigObj(filename, configspec=configspec)
+ {-coloring}
+
+.. _validate.py documentation: http://www.voidspace.org.uk/python/validate.html
+
+
+Type Conversion
+---------------
+
+By default, validation does type conversion. This means that if you specify
+``integer`` as the check, then calling validate_ will actually change the value
+to an integer (so long as the check succeeds).
+
+It also means that when you call the write_ method, the value will be converted
+back into a string using the ``str`` function.
+
+To switch this off, and leave values as strings after validation, you need to
+set the stringify_ attribute to ``False``. If this is the case, attempting to
+set a value to a non-string will raise an error.
+
+
+Default Values
+--------------
+
+You can set a default value in your check. If the value is missing from the
+config file then this value will be used instead. This means that your user
+only has to supply values that differ from the defaults.
+
+If you *don't* supply a default then for a value to be missing is an error,
+and this will show in the `return value`_ from validate.
+
+Additionally you can set the default to be ``None``. This means the value will
+be set to ``None`` (the object) *whichever check is used*. (It will be set to
+``''`` rather than ``None`` if stringify_ is ``False``). You can use this
+to easily implement optional values in your config files. ::
+
+ port = integer(0, 100, default=80)
+ user = string(max=25, default=0)
+ mode = option('quiet', 'loud', 'silent', default='loud')
+ nick = string(default=None)
+
+.. note::
+
+ Because the default goes through type conversion, it also has to pass the
+ check.
+
+ Note that ``default=None`` is case sensitive.
+
+
+List Values
+~~~~~~~~~~~
+
+It's possible that you will want to specify a list as a default value. To avoid
+confusing syntax with commas and quotes you use a list constructor to specify
+that keyword arguments are lists. This includes the ``default`` value. This
+makes checks look something like : ::
+
+ checkname(default=list('val1', 'val2', 'val3'))
+
+This works with all keyword arguments, but is most useful for default values.
+
+
+Repeated Sections
+-----------------
+
+Repeated sections are a way of specifying a configspec for a section that
+should be applied to *all* subsections in the same section.
+
+The easiest way of explaining this is to give an example. Suppose you have a
+config file that describes a dog. That dog has various attributes, but it can
+also have many fleas. You don't know in advance how many fleas there will be,
+or what they will be called, but you want each flea validated against the same
+configspec.
+
+We can define a section called *fleas*. We want every flea in that section
+(every sub-section) to have the same configspec applied to it. We do this by
+defining a single section called ``__many__``. ::
+
+ [dog]
+ name = string(default=Rover)
+ age = float(0, 99, default=0)
+
+ [[fleas]]
+
+ [[[__many__]]]
+ bloodsucker = boolean(default=True)
+ children = integer(default=10000)
+ size = option(small, tiny, micro, default=tiny)
+
+Every flea on our dog will now be validated using the ``__many__`` configspec.
+
+If you define another sub-section in a section *as well as* a ``__many__`` then
+you will get an error.
+
+``__many__`` sections can have sub-sections, including their own ``__many__``
+sub-sections. Defaults work in the normal way in repeated sections.
+
+
+Copy Mode
+---------
+
+Because you can specify default values in your configspec, you can use
+ConfigObj to write out default config files for your application.
+
+However, normally values supplied from a default in a configspec are *not*
+written out by the ``write`` method.
+
+To do this, you need to specify ``copy=True`` when you call validate. As well
+as not marking values as default, all the comments in the configspec file
+will be copied into your ConfigObj instance.
+
+.. raw:: html
+
+ {+coloring}
+ from configobj import ConfigObj
+ from validate import Validator
+ vdt = Validator()
+ config = ConfigObj(configspec='default.ini')
+ config.filename = 'new_default.ini'
+ config.validate(vdt, copy=True)
+ config.write()
+ {-coloring}
+
+
+Validation and Interpolation
+----------------------------
+
+String interpolation and validation don't play well together. When validation
+changes type it sets the value. If the value uses interpolation, then the
+interpolation reference would normally be overwritten. Calling ``write`` would
+then use the absolute value and the interpolation reference would be lost.
+
+As a compromise - if the value is unchanged by validation then it is not reset.
+This means strings that pass through validation unmodified will not be
+overwritten. If validation changes type - the value has to be overwritten, and
+any interpolation references are lost {sm;:-(}.
+
+
+SimpleVal
+---------
+
+You may not need a full validation process, but still want to check if all the
+expected values are present.
+
+Provided as part of the ConfigObj module is the ``SimpleVal`` object. This has
+a dummy ``test`` method that always passes.
+
+The only reason a test will fail is if the value is missing. The return value
+from ``validate`` will either be ``True``, meaning all present, or a dictionary
+with ``False`` for all missing values/sections.
+
+To use it, you still need to pass in a valid configspec when you create the
+ConfigObj, but just set all the values to ``''``. Then create an instance of
+``SimpleVal`` and pass it to the ``validate`` method.
+
+As a trivial example if you had the following config file : ::
+
+ # config file for an application
+ port = 80
+ protocol = http
+ domain = voidspace
+ top_level_domain = org.uk
+
+You would write the following configspec : ::
+
+ port = ''
+ protocol = ''
+ domain = ''
+ top_level_domain = ''
+
+.. raw:: html
+
+ {+coloring}
+
+ config = Configobj(filename, configspec=configspec)
+ val = SimpleVal()
+ test = config.validate(val)
+ if test == True:
+ print 'All values present.'
+ elif test == False:
+ print 'No values present!'
+ else:
+ for entry in test:
+ if test[entry] == False:
+ print '"%s" missing.' % entry
+
+ {-coloring}
+
+
+Empty values
+============
+
+Many config files from other applications allow empty values. As of version
+4.3.0, ConfigObj will read these as an empty string.
+
+A new option/attribute has been added (``write_empty_values``) to allow
+ConfigObj to write empty strings as empty values.
+
+.. raw:: html
+
+ {+coloring}
+ from configobj import ConfigObj
+ cfg = '''
+ key =
+ key2 = # a comment
+ '''.splitlines()
+ config = ConfigObj(cfg)
+ print config
+ ConfigObj({'key': '', 'key2': ''})
+
+ config.write_empty_values = True
+ for line in config.write():
+ print line
+
+ key =
+ key2 = # a comment
+ {-coloring}
+
+
+unrepr mode
+===========
+
+The ``unrepr`` option allows you to store and retrieve the basic Python
+data-types using config files. It has to use a slightly different syntax to
+normal ConfigObj files. Unsurprisingly it uses Python syntax.
+
+This means that lists are different (they are surrounded by square brackets),
+and strings *must* be quoted.
+
+The types that ``unrepr`` can work with are :
+
+ | strings, lists tuples
+ | None, True, False
+ | dictionaries, integers, floats
+ | longs and complex numbers
+
+You can't store classes, types or instances.
+
+``unrepr`` uses ``repr(object)`` to write out values, so it currently *doesn't*
+check that you are writing valid objects. If you attempt to read an unsupported
+value, ConfigObj will raise a ``configobj.UnknownType`` exception.
+
+Values that are triple quoted cased. The triple quotes are removed *before*
+converting. This means that you can use triple quotes to write dictionaries
+over several lines in your config files. They won't be written like this
+though.
+
+If you are writing config files by hand, for use with ``unrepr``, you should
+be aware of the following differences from normal ConfigObj syntax :
+
+ | List : ``['A List', 'With', 'Strings']``
+ | Strings : ``"Must be quoted."``
+ | Backslash : ``"The backslash must be escaped \\"``
+
+These all follow normal Python syntax.
+
+In unrepr mode *inline comments* are not saved. This is because lines are
+parsed using the `compiler package <http://docs.python.org/lib/compiler.html>`_
+which discards comments.
+
+
+String Interpolation
+====================
+
+ConfigObj allows string interpolation *similar* to the way ``ConfigParser``
+or ``string.Template`` work. The value of the ``interpolation`` attribute
+determines which style of interpolation you want to use. Valid values are
+"ConfigParser" or "Template" (case-insensitive, so "configparser" and
+"template" will also work). For backwards compatibility reasons, the value
+``True`` is also a valid value for the ``interpolation`` attribute, and
+will select ``ConfigParser``-style interpolation. At some undetermined point
+in the future, that default *may* change to ``Template``-style interpolation.
+
+For ``ConfigParser``-style interpolation, you specify a value to be
+substituted by including ``%(name)s`` in the value.
+
+For ``Template``-style interpolation, you specify a value to be substituted
+by including ``${cl}name{cr}`` in the value. Alternately, if 'name' is a valid
+Python identifier (i.e., is composed of nothing but alphanumeric characters,
+plus the underscore character), then the braces are optional and the value
+can be written as ``$name``.
+
+Note that ``ConfigParser``-style interpolation and ``Template``-style
+interpolation are mutually exclusive; you cannot have a configuration file
+that's a mix of one or the other. Pick one and stick to it. ``Template``-style
+interpolation is simpler to read and write by hand, and is recommended if
+you don't have a particular reason to use ``ConfigParser``-style.
+
+Interpolation checks first the current section to see if ``name`` is the key
+to a value. ('name' is case sensitive).
+
+If it doesn't find it, next it checks the 'DEFAULT' sub-section of the current
+section.
+
+If it still doesn't find it, it moves on to check the parent section and the
+parent section's 'DEFAULT' subsection, and so on all the way up to the main
+section.
+
+If the value specified isn't found in any of these locations, then a
+``MissingInterpolationOption`` error is raised (a subclass of
+``ConfigObjError``).
+
+If it is found then the returned value is also checked for substitutions. This
+allows you to make up compound values (for example directory paths) that use
+more than one default value. It also means it's possible to create circular
+references. If there are any circular references which would cause an infinite
+interpolation loop, an ``InterpolationLoopError`` is raised.
+
+Both of these errors are subclasses of ``InterpolationError``, which is a
+subclass of ``ConfigObjError``.
+
+String interpolation and validation don't play well together. This is because
+validation overwrites values - and so may erase the interpolation references.
+See `Validation and Interpolation`_. (This can only happen if validation
+has to *change* the value).
+
+
+Comments
+========
+
+Any line that starts with a '#', possibly preceded by whitespace, is a comment.
+
+If a config file starts with comments then these are preserved as the
+initial_comment_.
+
+If a config file ends with comments then these are preserved as the
+final_comment_.
+
+Every key or section marker may have lines of comments immediately above it.
+These are saved as the ``comments`` attribute of the section. Each member is a
+list of lines.
+
+You can also have a comment inline with a value. These are saved as the
+``inline_comments`` attribute of the section, with one entry per member of the
+section.
+
+Subsections (section markers in the config file) can also have comments.
+
+See `Section Attributes`_ for more on these attributes.
+
+These comments are all written back out by the ``write`` method.
+
+
+flatten_errors
+==============
+
+::
+
+ flatten_errors(cfg, res)
+
+Validation_ is a powerful way of checking that the values supplied by the user
+make sense.
+
+The validate_ method returns a results dictionary that represents pass or fail
+for each value. This doesn't give you any information about *why* the check
+failed.
+
+``flatten_errors`` is an example function that turns a results dictionary into
+a flat list, that only contains values that *failed*.
+
+``cfg`` is the ConfigObj instance being checked, ``res`` is the results
+dictionary returned by ``validate``.
+
+It returns a list of keys that failed. Each member of the list is a tuple : ::
+
+ ([list of sections...], key, result)
+
+If ``validate`` was called with ``preserve_errors=False`` (the default)
+then ``result`` will always be ``False``.
+
+*list of sections* is a flattened list of sections that the key was found
+in.
+
+If the section was missing then key will be ``None``.
+
+If the value (or section) was missing then ``result`` will be ``False``.
+
+If ``validate`` was called with ``preserve_errors=True`` and a value
+was present, but failed the check, then ``result`` will be the exception
+object returned. You can use this as a string that describes the failure.
+
+For example :
+
+ *The value "3" is of the wrong type*.
+
+
+Example Usage
+-------------
+
+The output from ``flatten_errors`` is a list of tuples.
+
+Here is an example of how you could present this information to the user.
+
+.. raw:: html
+
+ {+coloring}
+
+ vtor = validate.Validator()
+ # ini is your config file - cs is the configspec
+ cfg = ConfigObj(ini, configspec=cs)
+ res = cfg.validate(vtor, preserve_errors=True)
+ for entry in flatten_errors(cfg, res):
+ # each entry is a tuple
+ section_list, key, error = entry
+ if key is not None:
+ section_list.append(key)
+ else:
+ section_list.append('[missing section]')
+ section_string = ', '.join(section_list)
+ if error == False:
+ error = 'Missing value or section.'
+ print section_string, ' = ', error
+
+ {-coloring}
+
+
+ConfigObj 3
+===========
+
+ConfigObj 3 is now deprecated in favour of ConfigObj 4. I can fix bugs in
+ConfigObj 3 if needed, though.
+
+For anyone who still needs it, you can download it here: `ConfigObj 3.3.1`_
+
+You can read the old docs at : `ConfigObj 3 Docs`_
+
+.. _ConfigObj 3.3.1: http://www.voidspace.org.uk/cgi-bin/voidspace/downman.py?file=configobj3.zip
+.. _ConfigObj 3 Docs: http://www.voidspace.org.uk/python/configobj3.html
+
+
+CREDITS
+=======
+
+ConfigObj 4 is written by (and copyright) `Michael Foord`_ and
+`Nicola Larosa`_.
+
+Particularly thanks to Nicola Larosa for help on the config file spec, the
+validation system and the doctests.
+
+*validate.py* was originally written by Michael Foord and Mark Andrews.
+
+Thanks to others for input and bugfixes.
+
+
+LICENSE
+=======
+
+ConfigObj, and related files, are licensed under the BSD license. This is a
+very unrestrictive license, but it comes with the usual disclaimer. This is
+free software: test it, break it, just don't blame us if it eats your data !
+Of course if it does, let us know and we'll fix the problem so it doesn't
+happen to anyone else {sm;:-)}. ::
+
+ Copyright (c) 2004 - 2008, Michael Foord & Nicola Larosa
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ * Neither the name of Michael Foord nor Nicola Larosa
+ may be used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+You should also be able to find a copy of this license at : `BSD License`_
+
+.. _BSD License: http://www.voidspace.org.uk/python/license.shtml
+
+
+TODO
+====
+
+Better support for configuration from multiple files, including tracking
+*where* the original file came from and writing changes to the correct
+file.
+
+Make ``newline`` an option (as well as an attribute) ?
+
+``UTF16`` encoded files, when returned as a list of lines, will have the
+BOM at the start of every line. Should this be removed from all but the
+first line ?
+
+Option to set warning type for unicode decode ? (Defaults to strict).
+
+A method to optionally remove uniform indentation from multiline values.
+(do as an example of using ``walk`` - along with string-escape)
+
+Should the results dictionary from validate be an ordered dictionary if
+`odict <http://www.voidspace.org.uk/python/odict.html>`_ is available ?
+
+Implement some of the sequence methods (which include slicing) from the
+newer ``odict`` ?
+
+Preserve line numbers of values (and possibly the original text of each value).
+
+
+ISSUES
+======
+
+.. note::
+
+ Please file any bug reports to `Michael Foord`_ or the **ConfigObj**
+ `Mailing List`_.
+
+There is currently no way to specify the encoding of a configspec file.
+
+When using ``copy`` mode for validation, it won't copy ``DEFAULT``
+sections. This is so that you *can* use interpolation in configspec
+files.
+
+``validate`` doesn't report *extra* values or sections.
+
+You can't have a keyword with the same name as a section (in the same
+section). They are both dictionary keys - so they would overlap.
+
+ConfigObj doesn't quote and unquote values if ``list_values=False``.
+This means that leading or trailing whitespace in values will be lost when
+writing. (Unless you manually quote).
+
+Interpolation checks first the current section, then the 'DEFAULT' subsection
+of the current section, before moving on to the current section's parent and
+so on up the tree.
+
+Does it matter that we don't support the ':' divider, which is supported
+by ``ConfigParser`` ?
+
+String interpolation and validation don't play well together. When
+validation changes type it sets the value. This will correctly fetch the
+value using interpolation - but then overwrite the interpolation reference.
+If the value is unchanged by validation (it's a string) - but other types
+will be.
+
+
+CHANGELOG
+=========
+
+This is an abbreviated changelog showing the major releases up to version 4.
+From version 4 it lists all releases and changes.
+
+
+2008/06/27 - Version 4.5.3
+--------------------------
+
+BUGFIX: fixed a problem with ``copy=True`` when validating with configspecs that use
+``__many__`` sections.
+
+
+2008/02/05 - Version 4.5.2
+--------------------------
+
+Distribution updated to include version 0.3.2 of validate_. This means that
+``None`` as a default value win configspecs works.
+
+
+2008/02/05 - Version 4.5.1
+--------------------------
+
+Distribution updated to include version 0.3.1 of validate_. This means that
+Unicode configspecs now work.
+
+
+2008/02/05 - Version 4.5.0
+--------------------------
+
+ConfigObj will now guarantee that files will be written terminated with a
+newline.
+
+ConfigObj will no longer attempt to import the ``validate`` module, until/unless
+you call ``ConfigObj.validate`` with ``preserve_errors=True``. This makes it
+faster to import.
+
+New methods ``restore_default`` and ``restore_defaults``. ``restore_default``
+resets an entry to its default value (and returns that value). ``restore_defaults``
+resets all entries to their default value. It doesn't modify entries without a
+default value. You must have validated a ConfigObj (which populates the
+``default_values`` dictionary) before calling these methods.
+
+BUGFIX: Proper quoting of keys, values and list values that contain hashes
+(when writing). When ``list_values=False``, values containing hashes are
+triple quoted.
+
+Added the ``reload`` method. This reloads a ConfigObj from file. If the filename
+attribute is not set then a ``ReloadError`` (a new exception inheriting from
+``IOError``) is raised.
+
+BUGFIX: Files are read in with 'rb' mode, so that native/non-native line endings work!
+
+Minor efficiency improvement in ``unrepr`` mode.
+
+Added missing docstrings for some overidden dictionary methods.
+
+Added the ``reset`` method. This restores a ConfigObj to a freshly created state.
+
+Removed old CHANGELOG file.
+
+
+2007/02/04 - Version 4.4.0
+--------------------------
+
+Official release of 4.4.0
+
+
+2006/12/17 - Version 4.3.3-alpha4
+---------------------------------
+
+By Nicola Larosa
+
+Allowed arbitrary indentation in the ``indent_type`` parameter, removed the
+``NUM_INDENT_SPACES`` and ``MAX_INTERPOL_DEPTH`` (a leftover) constants,
+added indentation tests (including another docutils workaround, sigh), updated
+the documentation.
+
+By Michael Foord
+
+Made the import of ``compiler`` conditional so that ``ConfigObj`` can be used
+with `IronPython <http://www.codeplex.com/IronPython>`_.
+
+
+2006/12/17 - Version 4.3.3-alpha3
+---------------------------------
+
+By Nicola Larosa
+
+Added a missing ``self.`` in the _handle_comment method and a related test,
+per Sourceforge bug #1523975.
+
+
+2006/12/09 - Version 4.3.3-alpha2
+---------------------------------
+
+By Nicola Larosa
+
+Changed interpolation search strategy, based on this patch by Robin Munn:
+http://sourceforge.net/mailarchive/message.php?msg_id=17125993
+
+
+2006/12/09 - Version 4.3.3-alpha1
+---------------------------------
+
+By Nicola Larosa
+
+Added Template-style interpolation, with tests, based on this patch by
+Robin Munn: http://sourceforge.net/mailarchive/message.php?msg_id=17125991
+(awful archives, bad Sourceforge, bad).
+
+
+2006/06/04 - Version 4.3.2
+--------------------------
+
+Changed error handling, if parsing finds a single error then that error will
+be re-raised. That error will still have an ``errors`` and a ``config``
+attribute.
+
+Fixed bug where '\\n' terminated files could be truncated.
+
+Bugfix in ``unrepr`` mode, it couldn't handle '#' in values. (Thanks to
+Philippe Normand for the report.)
+
+As a consequence of this fix, ConfigObj doesn't now keep inline comments in
+``unrepr`` mode. This is because the parser in the `compiler package`_
+doesn't keep comments. {sm;:-)}
+
+Error messages are now more useful. They tell you the number of parsing errors
+and the line number of the first error. (In the case of multiple errors.)
+
+Line numbers in exceptions now start at 1, not 0.
+
+Errors in ``unrepr`` mode are now handled the same way as in the normal mode.
+The errors stored will be an ``UnreprError``.
+
+
+2006/04/29 - Version 4.3.1
+--------------------------
+
+Added ``validate.py`` back into ``configobj.zip``. (Thanks to Stewart
+Midwinter)
+
+Updated to `validate.py`_ 0.2.2.
+
+Preserve tuples when calling the ``dict`` method. (Thanks to Gustavo Niemeyer.)
+
+Changed ``__repr__`` to return a string that contains ``ConfigObj({ ... })``.
+
+Change so that an options dictionary isn't modified by passing it to ConfigObj.
+(Thanks to Artarious.)
+
+Added ability to handle negative integers in ``unrepr``. (Thanks to Kevin
+Dangoor.)
+
+
+2006/03/24 - Version 4.3.0
+--------------------------
+
+Moved the tests and the CHANGELOG (etc) into a separate file. This has reduced
+the size of ``configobj.py`` by about 40%.
+
+Added the ``unrepr`` mode to reading and writing config files. Thanks to Kevin
+Dangoor for this suggestion.
+
+Empty values are now valid syntax. They are read as an empty string ``''``.
+(``key =``, or ``key = # comment``.)
+
+``validate`` now honours the order of the configspec.
+
+Added the ``copy`` mode to validate. Thanks to Louis Cordier for this
+suggestion.
+
+Fixed bug where files written on windows could be given ``'\r\r\n'`` line
+terminators.
+
+Fixed bug where last occurring comment line could be interpreted as the
+final comment if the last line isn't terminated.
+
+Fixed bug where nested list values would be flattened when ``write`` is
+called. Now sub-lists have a string representation written instead.
+
+Deprecated ``encode`` and ``decode`` methods instead.
+
+You can now pass in a ConfigObj instance as a configspec (remember to read
+the configspec file using ``list_values=False``).
+
+Sorted footnotes in the docs.
+
+
+2006/02/16 - Version 4.2.0
+--------------------------
+
+Removed ``BOM_UTF8`` from ``__all__``.
+
+The ``BOM`` attribute has become a boolean. (Defaults to ``False``.) It is
+*only* ``True`` for the ``UTF16/UTF8`` encodings.
+
+File like objects no longer need a ``seek`` attribute.
+
+Full unicode support added. New options/attributes ``encoding``,
+``default_encoding``.
+
+ConfigObj no longer keeps a reference to file like objects. Instead the
+``write`` method takes a file like object as an optional argument. (Which
+will be used in preference of the ``filename`` attribute if that exists as
+well.)
+
+utf16 files decoded to unicode.
+
+If ``BOM`` is ``True``, but no encoding specified, then the utf8 BOM is
+written out at the start of the file. (It will normally only be ``True`` if
+the utf8 BOM was found when the file was read.)
+
+Thanks to Aaron Bentley for help and testing on the unicode issues.
+
+File paths are *not* converted to absolute paths, relative paths will
+remain relative as the ``filename`` attribute.
+
+Fixed bug where ``final_comment`` wasn't returned if ``write`` is returning
+a list of lines.
+
+Deprecated ``istrue``, replaced it with ``as_bool``.
+
+Added ``as_int`` and ``as_float``.
+
+
+2005/12/14 - Version 4.1.0
+--------------------------
+
+Added ``merge``, a recursive update.
+
+Added ``preserve_errors`` to ``validate`` and the ``flatten_errors``
+example function.
+
+Thanks to Matthew Brett for suggestions and helping me iron out bugs.
+
+Fixed bug where a config file is *all* comment, the comment will now be
+``initial_comment`` rather than ``final_comment``.
+
+Validation no longer done on the 'DEFAULT' section (only in the root level).
+This allows interpolation in configspecs.
+
+Also use the new list syntax in validate_ 0.2.1. (For configspecs).
+
+
+2005/12/02 - Version 4.0.2
+--------------------------
+
+Fixed bug in ``create_empty``. Thanks to Paul Jimenez for the report.
+
+
+2005/11/05 - Version 4.0.1
+--------------------------
+
+Fixed bug in ``Section.walk`` when transforming names as well as values.
+
+Added the ``istrue`` method. (Fetches the boolean equivalent of a string
+value).
+
+Fixed ``list_values=False`` - they are now only quoted/unquoted if they
+are multiline values.
+
+List values are written as ``item, item`` rather than ``item,item``.
+
+
+2005/10/17 - Version 4.0.0
+--------------------------
+
+**ConfigObj 4.0.0 Final**
+
+Fixed bug in ``setdefault``. When creating a new section with setdefault the
+reference returned would be to the dictionary passed in *not* to the new
+section. Bug fixed and behaviour documented.
+
+Obscure typo/bug fixed in ``write``. Wouldn't have affected anyone though.
+
+
+2005/09/09 - Version 4.0.0 beta 5
+---------------------------------
+
+Removed ``PositionError``.
+
+Allowed quotes around keys as documented.
+
+Fixed bug with commas in comments. (matched as a list value)
+
+
+2005/09/07 - Version 4.0.0 beta 4
+---------------------------------
+
+Fixed bug in ``__delitem__``. Deleting an item no longer deletes the
+``inline_comments`` attribute.
+
+Fixed bug in initialising ConfigObj from a ConfigObj.
+
+Changed the mailing list address.
+
+
+2005/08/28 - Version 4.0.0 beta 3
+---------------------------------
+
+Interpolation is switched off before writing out files.
+
+Fixed bug in handling ``StringIO`` instances. (Thanks to report from
+Gustavo Niemeyer.)
+
+Moved the doctests from the ``__init__`` method to a separate function.
+(For the sake of IDE calltips).
+
+
+2005/08/25 - Version 4.0.0 beta 2
+---------------------------------
+
+Amendments to *validate.py*.
+
+First public release.
+
+
+2005/08/21 - Version 4.0.0 beta 1
+---------------------------------
+
+Reads nested subsections to any depth.
+
+Multiline values.
+
+Simplified options and methods.
+
+New list syntax.
+
+Faster, smaller, and better parser.
+
+Validation greatly improved. Includes:
+
+ * type conversion
+ * default values
+ * repeated sections
+
+Improved error handling.
+
+Plus lots of other improvements. {sm;:grin:}
+
+
+2004/05/24 - Version 3.0.0
+--------------------------
+
+Several incompatible changes: another major overhaul and change. (Lots of
+improvements though).
+
+Added support for standard config files with sections. This has an entirely
+new interface: each section is a dictionary of values.
+
+Changed the update method to be called writein: update clashes with a dict
+method.
+
+Made various attributes keyword arguments, added several.
+
+Configspecs and orderlists have changed a great deal.
+
+Removed support for adding dictionaries: use update instead.
+
+Now subclasses a new class called caselessDict. This should add various
+dictionary methods that could have caused errors before.
+
+It also preserves the original casing of keywords when writing them back out.
+
+Comments are also saved using a ``caselessDict``.
+
+Using a non-string key will now raise a ``TypeError`` rather than converting
+the key.
+
+Added an exceptions keyword for *much* better handling of errors.
+
+Made ``creatempty=False`` the default.
+
+Now checks indict *and* any keyword args. Keyword args take precedence over
+indict.
+
+``' ', ':', '=', ','`` and ``'\t'`` are now all valid dividers where the
+keyword is unquoted.
+
+ConfigObj now does no type checking against configspec when you set items.
+
+delete and add methods removed (they were unnecessary).
+
+Docs rewritten to include all this gumph and more; actually ConfigObj is
+*really* easy to use.
+
+Support for stdout was removed.
+
+A few new methods added.
+
+Charmap is now incorporated into ConfigObj.
+
+
+2004/03/14 - Version 2.0.0 beta
+-------------------------------
+
+Re-written it to subclass dict. My first forays into inheritance and operator
+overloading.
+
+The config object now behaves like a dictionary.
+
+I've completely broken the interface, but I don't think anyone was really
+using it anyway.
+
+This new version is much more 'classy'. {sm;:wink:}
+
+It will also read straight from/to a filename and completely parse a config
+file without you *having* to supply a config spec.
+
+Uses listparse, so can handle nested list items as values.
+
+No longer has getval and setval methods: use normal dictionary methods, or add
+and delete.
+
+
+2004/01/29 - Version 1.0.5
+--------------------------
+
+Version 1.0.5 has a couple of bugfixes as well as a couple of useful additions
+over previous versions.
+
+Since 1.0.0 the buildconfig function has been moved into this distribution,
+and the methods reset, verify, getval and setval have been added.
+
+A couple of bugs have been fixed.
+
+
+Origins
+-------
+
+ConfigObj originated in a set of functions for reading config files in the
+`atlantibots <http://www.voidspace.org.uk/atlantibots/>`_ project. The original
+functions were written by Rob McNeur.
+
+
+----------
+
+
+Footnotes
+=========
+
+.. [#] And if you discover any bugs, let us know. We'll fix them quickly.
+
+.. [#] If you specify a filename that doesn't exist, ConfigObj will assume you
+ are creating a new one. See the *create_empty* and *file_error* options_.
+
+.. [#] They can be byte strings (*ordinary* strings) or Unicode.
+
+.. [#] Except we don't support the RFC822 style line continuations, nor ':' as
+ a divider.
+
+.. [#] This is a change in ConfigObj 4.2.0. Note that ConfigObj doesn't call
+ the seek method of any file like object you pass in. You may want to call
+ ``file_object.seek(0)`` yourself, first.
+
+.. [#] A side effect of this is that it enables you to copy a ConfigObj :
+
+ .. raw:: html
+
+ {+coloring}
+
+ # only copies members
+ # not attributes/comments
+ config2 = ConfigObj(config1)
+
+ {-coloring}
+
+ The order of values and sections will not be preserved, though.
+
+.. [#] Other than lists of strings.
+
+.. [#] The exception is if it detects a ``UTF16`` encoded file which it
+ must decode before parsing.
+
+.. [#] The method signature shows that this method takes
+ two arguments. The second is the section to be written. This is because the
+ ``write`` method is called recursively.
+
+.. [#] The dict method doesn't actually use the deepcopy mechanism. This means
+ if you add nested lists (etc) to your ConfigObj, then the dictionary
+ returned by dict may contain some references. For all *normal* ConfigObjs
+ it will return a deepcopy.
+
+.. [#] Passing ``(section, key)`` rather than ``(value, key)`` allows you to
+ change the value by setting ``section[key] = newval``. It also gives you
+ access to the *rename* method of the section.
+
+.. [#] Minimum required version of *validate.py* 0.2.0 .
+
+
+.. note::
+
+ Rendering this document with docutils also needs the
+ textmacros module and the PySrc CSS stuff. See
+ http://www.voidspace.org.uk/python/firedrop2/textmacros.shtml
+
+
+.. raw:: html
+
+ <div align="center">
+ <p>
+ <a href="http://www.python.org">
+ <img src="images/new_python.gif" width="100" height="103" border="0"
+ alt="Powered by Python" />
+ </a>
+ <a href="http://sourceforge.net">
+ <img src="http://sourceforge.net/sflogo.php?group_id=123265&amp;type=1" width="88" height="31" border="0" alt="SourceForge.net Logo" />
+ </a>
+ <a href="http://www.opensource.org">
+ <img src="images/osi-certified-120x100.gif" width="120" height="100" border="0"
+ alt="Certified Open Source"/>
+ </a>
+ </p>
+ <p>
+ <a href="http://www.voidspace.org.uk/python/index.shtml">
+ <img src="images/pythonbanner.gif" width="468" height="60"
+ alt="Python on Voidspace" border="0" />
+ </a>
+ </p>
+
+ </div>
+
+.. _listquote: http://www.voidspace.org.uk/python/modules.shtml#listquote
+.. _Michael Foord: http://www.voidspace.org.uk/python/weblog/index.shtml
+.. _Nicola Larosa: http://www.teknico.net
diff --git a/docs/validate.txt b/docs/validate.txt
new file mode 100644
index 0000000..cbb3ce2
--- /dev/null
+++ b/docs/validate.txt
@@ -0,0 +1,735 @@
+===================================
+ Validation Schema with validate.py
+===================================
+
+--------------------------
+ Using the Validator class
+--------------------------
+
+
+:Authors: `Michael Foord`_, `Nicola Larosa`_, `Mark Andrews`_
+:Version: Validate 0.3.2
+:Date: 2008/02/24
+:Homepage: `Validate Homepage`_
+:License: `BSD License`_
+:Support: `Mailing List`_
+
+.. _Mailing List: http://lists.sourceforge.net/lists/listinfo/configobj-develop
+.. _Michael Foord: fuzzyman@voidspace.org.uk
+.. _Nicola Larosa: nico@teknico.net
+.. _This Document:
+.. _Validate Homepage: http://www.voidspace.org.uk/python/validate.html
+.. _BSD License: http://www.voidspace.org.uk/python/license.shtml
+
+
+.. contents:: Validate Manual
+.. sectnum::
+
+
+Introduction
+============
+
+Validation is used to check that supplied values conform to a specification.
+
+The value can be supplied as a string, e.g. from a config file. In this case
+the check will also *convert* the value to the required type. This allows you
+to add validation as a transparent layer to access data stored as strings. The
+validation checks that the data is correct *and* converts it to the expected
+type.
+
+Checks are also strings, and are easy to write. One generic system can be used
+to validate information from different sources via a single consistent
+mechanism.
+
+Checks look like function calls, and map to function calls. They can include
+parameters and keyword arguments. These arguments are passed to the relevant
+function by the ``Validator`` instance, along with the value being checked.
+
+The syntax for checks also allows for specifying a default value. This default
+value can be ``None``, no matter what the type of the check. This can be used
+to indicate that a value was missing, and so holds no useful value.
+
+Functions either return a new value, or raise an exception. See `Validator
+Exceptions`_ for the low down on the exception classes that ``validate.py``
+defines.
+
+Some standard functions are provided, for basic data types; these come built
+into every validator. Additional checks are easy to write: they can be provided
+when the ``Validator`` is instantiated, or added afterwards.
+
+Validate was primarily written to support ConfigObj_, but is designed to be
+applicable to many other situations.
+
+For support and bug reports please use the ConfigObj `Mailing List`_.
+
+.. _ConfigObj: http://www.voidspace.org.uk/python/configobj.html
+
+
+Downloading
+===========
+
+The current version is **0.3.2**, dated 24th February 2008.
+
+You can get obtain validate in the following ways :
+
+
+Files
+-----
+
+* validate.py_ from Voidspace
+
+* configobj.zip from Voidspace - See the homepage of ConfigObj_ for the latest
+ version and download links.
+
+ This contains validate.py and `this document`_. (As well as ConfigObj_ and
+ the ConfigObj documentation).
+
+* The latest development version can be obtained from the `Subversion Repository`_.
+
+
+Documentation
+-------------
+
+*configobj.zip* contains `this document`_.
+
+* You can view `this document`_ online as the `Validate Homepage`_.
+
+
+Pythonutils
+-----------
+
+Validate_ is also part of the Pythonutils_ set of modules. This contains
+various other useful helper modules, and is required by many of the `Voidspace
+Python Projects`_.
+
+.. _configobj.py: http://www.voidspace.org.uk/cgi-bin/voidspace/downman.py?file=configobj.py
+.. _configobj.zip: http://www.voidspace.org.uk/cgi-bin/voidspace/downman.py?file=configobj-4.5.3.zip
+.. _validate.py: http://www.voidspace.org.uk/cgi-bin/voidspace/downman.py?file=validate.py
+.. _Subversion Repository: http://svn.pythonutils.python-hosting.com/trunk/pythonutils/
+.. _Sourceforge: http://sourceforge.net/projects/configobj
+.. _pythonutils: http://www.voidspace.org.uk/python/pythonutils.html
+.. _Voidspace Python Projects: http://www.voidspace.org.uk/python
+.. _validate: http://www.voidspace.org.uk/python/validate.html
+
+
+The standard functions
+======================
+
+The standard functions come built-in to every ``Validator`` instance. They work
+with the following basic data types :
+
+* integer
+* float
+* boolean
+* string
+* ip_addr
+
+plus lists of these datatypes.
+
+Adding additional checks is done through coding simple functions.
+
+The full set of standard checks are :
+
+:'integer': matches integer values (including negative). Takes optional 'min'
+ and 'max' arguments : ::
+
+ integer()
+ integer(3, 9) # any value from 3 to 9
+ integer(min=0) # any positive value
+ integer(max=9)
+
+:'float': matches float values
+ Has the same parameters as the integer check.
+
+:'boolean': matches boolean values: ``True`` or ``False``.
+ Acceptable string values for True are : ::
+
+ true, on, yes, 1
+
+ Acceptable string values for False are : ::
+
+ false, off, no, 0
+
+ Any other value raises an error.
+
+:'string': matches any string. Takes optional keyword args 'min' and 'max' to
+ specify min and max length of string.
+
+:'ip_addr': matches an Internet Protocol address, v.4, represented by a
+ dotted-quad string, i.e. '1.2.3.4'.
+
+:'list': matches any list. Takes optional keyword args 'min', and 'max' to
+ specify min and max sizes of the list. The list checks always
+ return a list.
+
+:'tuple': matches any list. This check returns a tuple rather than a list.
+
+:'int_list': Matches a list of integers. Takes the same arguments as list.
+
+:'float_list': Matches a list of floats. Takes the same arguments as list.
+
+:'bool_list': Matches a list of boolean values. Takes the same arguments as
+ list.
+
+:'string_list': Matches a list of strings. Takes the same arguments as list.
+
+:'ip_addr_list': Matches a list of IP addresses. Takes the same arguments as
+ list.
+
+:'mixed_list': Matches a list with different types in specific positions.
+ List size must match the number of arguments.
+
+ Each position can be one of : ::
+
+ int, str, boolean, float, ip_addr
+
+ So to specify a list with two strings followed by two integers,
+ you write the check as : ::
+
+ mixed_list(str, str, int, int)
+
+:'pass': matches everything: it never fails and the value is unchanged. It is
+ also the default if no check is specified.
+
+:'option': matches any from a list of options.
+ You specify this test with : ::
+
+ option('option 1', 'option 2', 'option 3')
+
+The following code will work without you having to specifically add the
+functions yourself.
+
+.. raw:: html
+
+ {+coloring}
+
+ from validate import Validator
+ #
+ vtor = Validator()
+ newval1 = vtor.check('integer', value1)
+ newval2 = vtor.check('boolean', value2)
+ # etc ...
+
+ {-coloring}
+
+.. note::
+
+ Of course, if these checks fail they raise exceptions. So you should wrap
+ them in ``try...except`` blocks. Better still, use ConfigObj for a higher
+ level interface.
+
+
+Using Validator
+===============
+
+Using ``Validator`` is very easy. It has one public attribute and one public
+method.
+
+Shown below are the different steps in using ``Validator``.
+
+The only additional thing you need to know, is about `Writing check
+functions`_.
+
+Instantiate
+-----------
+
+.. raw:: html
+
+ {+coloring}
+
+ from validate import Validator
+ vtor = Validator()
+
+ {-coloring}
+
+or even :
+
+.. raw:: html
+
+ {+coloring}
+
+ from validate import Validator
+ #
+ fdict = {
+ 'check_name1': function1,
+ 'check_name2': function2,
+ 'check_name3': function3,
+ }
+ #
+ vtor = Validator(fdict)
+
+ {-coloring}
+
+The second method adds a set of your functions as soon as your validator is
+created. They are stored in the ``vtor.functions`` dictionary. The 'key' you
+give them in this dictionary is the name you use in your checks (not the
+original function name).
+
+Dictionary keys/functions you pass in can override the built-in ones if you
+want.
+
+
+Adding functions
+----------------
+
+The code shown above, for adding functions on instantiation, has exactly the
+same effect as the following code :
+
+.. raw:: html
+
+ {+coloring}
+
+ from validate import Validator
+ #
+ vtor = Validator()
+ vtor.functions['check_name1'] = function1
+ vtor.functions['check_name2'] = function2
+ vtor.functions['check_name3'] = function3
+
+ {-coloring}
+
+``vtor.functions`` is just a dictionary that maps names to functions, so we
+could also have called ``vtor.functions.update(fdict)``.
+
+
+Writing the check
+-----------------
+
+As we've heard, the checks map to the names in the ``functions`` dictionary.
+You've got a full list of `The standard functions`_ and the arguments they
+take.
+
+If you're using ``Validator`` from ConfigObj, then your checks will look like
+: ::
+
+ keyword = int_list(max=6)
+
+but the check part will be identical .
+
+
+The check method
+----------------
+
+If you're not using ``Validator`` from ConfigObj, then you'll need to call the
+``check`` method yourself.
+
+If the check fails then it will raise an exception, so you'll want to trap
+that. Here's the basic example :
+
+.. raw:: html
+
+ {+coloring}
+
+ from validate import Validator, ValidateError
+ #
+ vtor = Validator()
+ check = "integer(0, 9)"
+ value = 3
+ try:
+ newvalue = vtor.check(check, value)
+ except ValidateError:
+ print 'Check Failed.'
+ else:
+ print 'Check passed.'
+
+ {-coloring}
+
+.. caution::
+
+ Although the value can be a string, if it represents a list it should
+ already have been turned into a list of strings.
+
+
+Default Values
+~~~~~~~~~~~~~~
+
+Some values may not be available, and you may want to be able to specify a
+default as part of the check.
+
+You do this by passing the keyword ``missing=True`` to the ``check`` method, as
+well as a ``default=value`` in the check. (Constructing these checks is done
+automatically by ConfigObj: you only need to know about the ``default=value``
+part) :
+
+.. raw:: html
+
+ {+coloring}
+
+ check1 = 'integer(default=50)'
+ check2 = 'option("val 1", "val 2", "val 3", default="val 1")'
+
+ assert vtor.check(check1, '', missing=True) == 50
+ assert vtor.check(check2, '', missing=True) == "val 1"
+
+ {-coloring}
+
+If you pass in ``missing=True`` to the check method, then the actual value is
+ignored. If no default is specified in the check, a ``ValidateMissingValue``
+exception is raised. If a default is specified then that is passed to the
+check instead.
+
+If the check has ``default=None`` (case sensitive) then ``vtor.check`` will
+*always* return ``None`` (the object). This makes it easy to tell your program
+that this check contains no useful value when missing, i.e. the value is
+optional, and may be omitted without harm.
+
+
+.. note::
+
+ As of version 0.3.0, if you specify ``default='None'`` (note the quote marks
+ around ``None``) then it will be interpreted as the string ``'None'``.
+
+
+List Values
+~~~~~~~~~~~
+
+It's possible that you would like your default value to be a list. It's even
+possible that you will write your own check functions - and would like to pass
+them keyword arguments as lists from within the check.
+
+To avoid confusing syntax with commas and quotes you use a list constructor to
+specify that keyword arguments are lists. This includes the ``default`` value.
+This makes checks look something like : ::
+
+ checkname(default=list('val1', 'val2', 'val3'))
+
+
+get_default_value
+-----------------
+
+``Validator`` instances have a ``get_default_value`` method. It takes a ``check`` string
+(the same string you would pass to the ``check`` method) and returns the default value,
+converted to the right type. If the check doesn't define a default value then this method
+raises a ``KeyError``.
+
+If the ``check`` has been seen before then it will have been parsed and cached already,
+so this method is not expensive to call (however the conversion is done each time).
+
+
+
+Validator Exceptions
+====================
+
+.. note::
+
+ If you only use Validator through ConfigObj, it traps these Exceptions for
+ you. You will still need to know about them for writing your own check
+ functions.
+
+``vtor.check`` indicates that the check has failed by raising an exception.
+The appropriate error should be raised in the check function.
+
+The base error class is ``ValidateError``. All errors (except for ``VdtParamError``)
+raised are sub-classes of this.
+
+If an unrecognised check is specified then ``VdtUnknownCheckError`` is
+raised.
+
+There are also ``VdtTypeError`` and ``VdtValueError``.
+
+If incorrect parameters are passed to a check function then it will (or should)
+raise ``VdtParamError``. As this indicates *programmer* error, rather than an error
+in the value, it is a subclass of ``SyntaxError`` instead of ``ValidateError``.
+
+.. note::
+
+ This means it *won't* be caught by ConfigObj - but propagated instead.
+
+If the value supplied is the wrong type, then the check should raise
+``VdtTypeError``. e.g. the check requires the value to be an integer (or
+representation of an integer) and something else was supplied.
+
+If the value supplied is the right type, but an unacceptable value, then the
+check should raise ``VdtValueError``. e.g. the check requires the value to
+be an integer (or representation of an integer) less than ten and a higher
+value was supplied.
+
+Both ``VdtTypeError`` and ``VdtValueError`` are initialised with the
+incorrect value. In other words you raise them like this :
+
+.. raw:: html
+
+ {+coloring}
+
+ raise VdtTypeError(value)
+ #
+ raise VdtValueError(value)
+
+ {-coloring}
+
+``VdtValueError`` has the following subclasses, which should be raised if
+they are more appropriate.
+
+* ``VdtValueTooSmallError``
+* ``VdtValueTooBigError``
+* ``VdtValueTooShortError``
+* ``VdtValueTooLongError``
+
+
+Writing check functions
+=======================
+
+Writing check functions is easy.
+
+The check function will receive the value as its first argument, followed by
+any other parameters and keyword arguments.
+
+If the check fails, it should raise a ``VdtTypeError`` or a
+``VdtValueError`` (or an appropriate subclass).
+
+All parameters and keyword arguments are *always* passed as strings. (Parsed
+from the check string).
+
+The value might be a string (or list of strings) and need
+converting to the right type - alternatively it might already be a list of
+integers. Our function needs to be able to handle either.
+
+If the check passes then it should return the value (possibly converted to the
+right type).
+
+And that's it !
+
+
+Example
+-------
+
+Here is an example function that requires a list of integers. Each integer
+must be between 0 and 99.
+
+It takes a single argument specifying the length of the list. (Which allows us
+to use the same check in more than one place). If the length can't be converted
+to an integer then we need to raise ``VdtParamError``.
+
+Next we check that the value is a list. Anything else should raise a
+``VdtTypeError``. The list should also have 'length' entries. If the list
+has more or less entries then we will need to raise a
+``VdtValueTooShortError`` or a ``VdtValueTooLongError``.
+
+Then we need to check every entry in the list. Each entry should be an integer
+between 0 and 99, or a string representation of an integer between 0 and 99.
+Any other type is a ``VdtTypeError``, any other value is a
+``VdtValueError`` (either too big, or too small).
+
+.. raw:: html
+
+ {+coloring}
+
+ def special_list(value, length):
+ """
+ Check that the supplied value is a list of integers,
+ with 'length' entries, and each entry between 0 and 99.
+ """
+ # length is supplied as a string
+ # we need to convert it to an integer
+ try:
+ length = int(length)
+ except ValueError:
+ raise VdtParamError('length', length)
+ #
+ # Check the supplied value is a list
+ if not isinstance(value, list):
+ raise VdtTypeError(value)
+ #
+ # check the length of the list is correct
+ if len(value) > length:
+ raise VdtValueTooLongError(value)
+ elif len(value) < length:
+ raise VdtValueTooShortError(value)
+ #
+ # Next, check every member in the list
+ # converting strings as necessary
+ out = []
+ for entry in value:
+ if not isinstance(entry, (str, unicode, int)):
+ # a value in the list
+ # is neither an integer nor a string
+ raise VdtTypeError(value)
+ elif isinstance(entry, (str, unicode)):
+ if not entry.isdigit():
+ raise VdtTypeError(value)
+ else:
+ entry = int(entry)
+ if entry < 0:
+ raise VdtValueTooSmallError(value)
+ elif entry > 99:
+ raise VdtValueTooBigError(value)
+ out.append(entry)
+ #
+ # if we got this far, all is well
+ # return the new list
+ return out
+
+ {-coloring}
+
+If you are only using validate from ConfigObj then the error type (*TooBig*,
+*TooSmall*, etc) is lost - so you may only want to raise ``VdtValueError``.
+
+.. caution::
+
+ If your function raises an exception that isn't a subclass of
+ ``ValidateError``, then ConfigObj won't trap it. This means validation will
+ fail.
+
+ This is why our function starts by checking the type of the value. If we
+ are passed the wrong type (e.g. an integer rather than a list) we get a
+ ``VdtTypeError`` rather than bombing out when we try to iterate over
+ the value.
+
+If you are using validate in another circumstance you may want to create your
+own subclasses of ``ValidateError``, that convey more specific information.
+
+
+Known Issues
+============
+
+The following parses and then blows up. The resulting error message
+is confusing:
+
+ ``checkname(default=list(1, 2, 3, 4)``
+
+This is because it parses as: ``checkname(default="list(1", 2, 3, 4)``.
+That isn't actually unreasonable, but the error message won't help you
+work out what has happened.
+
+
+TODO
+====
+
+* A regex check function ?
+* A timestamp check function ? (Using the ``parse`` function from ``DateUtil`` perhaps).
+
+
+ISSUES
+======
+
+.. note::
+
+ Please file any bug reports to `Michael Foord`_ or the ConfigObj
+ `Mailing List`_.
+
+If we could pull tuples out of arguments, it would be easier
+to specify arguments for 'mixed_lists'.
+
+
+CHANGELOG
+=========
+
+2008/02/24 - Version 0.3.2
+--------------------------
+
+BUGFIX: Handling of None as default value fixed.
+
+
+2008/02/05 - Version 0.3.1
+--------------------------
+
+BUGFIX: Unicode checks no longer broken.
+
+
+2008/02/05 - Version 0.3.0
+--------------------------
+
+Improved performance with a parse cache.
+
+New ``get_default_value`` method. Given a check it returns the default
+value (converted to the correct type) or raises a ``KeyError`` if the
+check doesn't specify a default.
+
+Added 'tuple' check and corresponding 'is_tuple' function (which always returns a tuple).
+
+BUGFIX: A quoted 'None' as a default value is no longer treated as None,
+but as the string 'None'.
+
+BUGFIX: We weren't unquoting keyword arguments of length two, so an
+empty string didn't work as a default.
+
+BUGFIX: Strings no longer pass the 'is_list' check. Additionally, the
+list checks always return lists.
+
+A couple of documentation bug fixes.
+
+Removed CHANGELOG from module.
+
+
+2007/02/04 Version 0.2.3
+-----------------------------
+
+Release of 0.2.3
+
+
+2006/12/17 Version 0.2.3-alpha1
+------------------------------------
+
+By Nicola Larosa
+
+Fixed validate doc to talk of ``boolean`` instead of ``bool``; changed the
+``is_bool`` function to ``is_boolean`` (Sourceforge bug #1531525).
+
+
+2006/04/29 Version 0.2.2
+-----------------------------
+
+Addressed bug where a string would pass the ``is_list`` test. (Thanks to
+Konrad Wojas.)
+
+
+2005/12/16 Version 0.2.1
+-----------------------------
+
+Fixed bug so we can handle keyword argument values with commas.
+
+We now use a list constructor for passing list values to keyword arguments
+(including ``default``) : ::
+
+ default=list("val", "val", "val")
+
+Added the ``_test`` test. {sm;:-)}
+
+Moved a function call outside a try...except block.
+
+
+2005/08/18 Version 0.2.0
+-----------------------------
+
+Updated by `Michael Foord`_ and `Nicola Larosa`_
+
+Does type conversion as well.
+
+
+2005/02/01 Version 0.1.0
+-----------------------------
+
+Initial version developed by `Michael Foord`_
+and Mark Andrews.
+
+.. note::
+
+ Rendering this document with docutils also needs the
+ textmacros module and the PySrc CSS stuff. See
+ http://www.voidspace.org.uk/python/firedrop2/textmacros.shtml
+
+
+.. raw:: html
+
+ <div align="center">
+ <p>
+ <a href="http://www.python.org">
+ <img src="images/new_python.gif" width="100" height="103" border="0"
+ alt="Powered by Python" />
+ </a>
+ <a href="http://sourceforge.net">
+ <img src="http://sourceforge.net/sflogo.php?group_id=123265&amp;type=1" width="88" height="31" border="0" alt="SourceForge.net Logo" />
+ </a>
+ <a href="http://www.opensource.org">
+ <img src="images/osi-certified-120x100.gif" width="120" height="100" border="0"
+ alt="Certified Open Source"/>
+ </a>
+ </p>
+ <p>
+ <a href="http://www.voidspace.org.uk/python/index.shtml">
+ <img src="images/pythonbanner.gif" width="468" height="60"
+ alt="Python on Voidspace" border="0" />
+ </a>
+ </p>
+ </div>
+
diff --git a/extras/ConfigPersist.py b/extras/ConfigPersist.py
new file mode 100644
index 0000000..42dca37
--- /dev/null
+++ b/extras/ConfigPersist.py
@@ -0,0 +1,242 @@
+# ConfigPersist.py
+# Functions for using ConfigObj for data persistence
+# Copyright (C) 2005 Michael Foord
+# E-mail: fuzzyman AT voidspace DOT org DOT uk
+
+# Released subject to the BSD License
+# Please see http://www.voidspace.org.uk/python/license.shtml
+
+# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
+# For information about bugfixes, updates and support, please join the
+# ConfigObj mailing list:
+# http://lists.sourceforge.net/lists/listinfo/configobj-develop
+# Comments, suggestions and bug reports welcome.
+
+"""
+Functions for doing data persistence with ConfigObj.
+
+It requires access to the validate module and ConfigObj.
+"""
+
+__version__ = '0.1.0'
+
+__all__ = (
+ 'add_configspec',
+ 'write_configspec',
+ 'add_typeinfo',
+ 'typeinfo_to_configspec',
+ 'vtor',
+ 'store',
+ 'restore',
+ 'save_configspec',
+ '__version__'
+ )
+
+from configobj import ConfigObj
+
+try:
+ from validate import Validator
+except ImportError:
+ vtor = None
+else:
+ vtor = Validator()
+
+def add_configspec(config):
+ """
+ A function that adds a configspec to a ConfigObj.
+
+ Will only work for ConfigObj instances using basic datatypes :
+
+ * floats
+ * strings
+ * ints
+ * booleans
+ * Lists of the above
+ """
+ config.configspec = {}
+ for entry in config:
+ val = config[entry]
+ if isinstance(val, dict):
+ # a subsection
+ add_configspec(val)
+ elif isinstance(val, bool):
+ config.configspec[entry] = 'boolean'
+ elif isinstance(val, int):
+ config.configspec[entry] = 'integer'
+ elif isinstance(val, float):
+ config.configspec[entry] = 'float'
+ elif isinstance(val, str):
+ config.configspec[entry] = 'string'
+ elif isinstance(val, (list, tuple)):
+ list_type = None
+ out_list = []
+ for mem in val:
+ if isinstance(mem, str):
+ this = 'string'
+ elif isinstance(mem, bool):
+ this = 'boolean'
+ elif isinstance(mem, int):
+ this = 'integer'
+ elif isinstance(mem, float):
+ this = 'float'
+ else:
+ raise TypeError('List member "%s" is an innapropriate type.' % mem)
+ if list_type and this != list_type:
+ list_type = 'mixed'
+ elif list_type is None:
+ list_type = this
+ out_list.append(this)
+ if list_type is None:
+ l = 'list(%s)'
+ else:
+ list_type = {'integer': 'int', 'boolean': 'bool',
+ 'mixed': 'mixed', 'float': 'float',
+ 'string': 'string' }[list_type]
+ l = '%s_list(%%s)' % list_type
+ config.configspec[entry] = l % str(out_list)[1:-1]
+ #
+ else:
+ raise TypeError('Value "%s" is an innapropriate type.' % val)
+
+def write_configspec(config):
+ """Return the configspec (of a ConfigObj) as a list of lines."""
+ out = []
+ for entry in config:
+ val = config[entry]
+ if isinstance(val, dict):
+ # a subsection
+ m = config.main._write_marker('', val.depth, entry, '')
+ out.append(m)
+ out += write_configspec(val)
+ else:
+ name = config.main._quote(entry, multiline=False)
+ out.append("%s = %s" % (name, config.configspec[entry]))
+ #
+ return out
+
+def add_typeinfo(config):
+ """
+ Turns the configspec attribute of each section into a member of the
+ section. (Called ``__types__``).
+
+ You must have already called ``add_configspec`` on the ConfigObj.
+ """
+ for entry in config.sections:
+ add_typeinfo(config[entry])
+ config['__types__'] = config.configspec
+
+def typeinfo_to_configspec(config):
+ """Turns the '__types__' member of each section into a configspec."""
+ for entry in config.sections:
+ if entry == '__types__':
+ continue
+ typeinfo_to_configspec(config[entry])
+ config.configspec = config['__types__']
+ del config['__types__']
+
+def store(config):
+ """"
+ Passed a ConfigObj instance add type info and save.
+
+ Returns the result of calling ``config.write()``.
+ """
+ add_configspec(config)
+ add_typeinfo(config)
+ return config.write()
+
+def restore(stored):
+ """
+ Restore a ConfigObj saved using the ``store`` function.
+
+ Takes a filename or list of lines, returns the ConfigObj instance.
+
+ Uses the built-in Validator instance of this module (vtor).
+
+ Raises an ImportError if the validate module isn't available
+ """
+ if vtor is None:
+ raise ImportError('Failed to import the validate module.')
+ config = ConfigObj(stored)
+ typeinfo_to_configspec(config)
+ config.validate(vtor)
+ return config
+
+def save_configspec(config):
+ """Creates a configspec and returns it as a list of lines."""
+ add_configspec(config)
+ return write_configspec(config)
+
+def _test():
+ """
+ A dummy function for the sake of doctest.
+
+ First test add_configspec
+ >>> from configobj import ConfigObj
+ >>> from validate import Validator
+ >>> vtor = Validator()
+ >>> config = ConfigObj()
+ >>> config['member 1'] = 3
+ >>> config['member 2'] = 3.0
+ >>> config['member 3'] = True
+ >>> config['member 4'] = [3, 3.0, True]
+ >>> add_configspec(config)
+ >>> assert config.configspec == { 'member 2': 'float',
+ ... 'member 3': 'boolean', 'member 1': 'integer',
+ ... 'member 4': "mixed_list('integer', 'float', 'boolean')"}
+ >>> assert config.validate(vtor) == True
+
+ Next test write_configspec - including a nested section
+ >>> config['section 1'] = config.copy()
+ >>> add_configspec(config)
+ >>> a = config.write()
+ >>> configspec = write_configspec(config)
+ >>> b = ConfigObj(a, configspec=configspec)
+ >>> assert b.validate(vtor) == True
+ >>> assert b == config
+
+ Next test add_typeinfo and typeinfo_to_configspec
+ >>> orig = ConfigObj(config)
+ >>> add_typeinfo(config)
+ >>> a = ConfigObj(config.write())
+ >>> typeinfo_to_configspec(a)
+ >>> assert a.validate(vtor) == True
+ >>> assert a == orig
+ >>> typeinfo_to_configspec(config)
+ >>> assert config.validate(vtor) == True
+ >>> assert config == orig
+
+ Test store and restore
+ >>> a = store(config)
+ >>> b = restore(a)
+ >>> assert b == orig
+
+ Test save_configspec
+ >>> a = save_configspec(orig)
+ >>> b = ConfigObj(b, configspec=a)
+ >>> b.validate(vtor)
+ 1
+ """
+
+if __name__ == '__main__':
+ # run the code tests in doctest format
+ #
+ import doctest
+ doctest.testmod()
+
+"""
+ISSUES
+======
+
+TODO
+====
+
+
+CHANGELOG
+=========
+
+2005/09/07
+----------
+
+Module created.
+
+""" \ No newline at end of file
diff --git a/extras/configpersist.txt b/extras/configpersist.txt
new file mode 100644
index 0000000..196b209
--- /dev/null
+++ b/extras/configpersist.txt
@@ -0,0 +1,266 @@
+=================================
+ Data Persistence with ConfigObj
+=================================
+--------------------------
+ The ConfigPersist Module
+--------------------------
+
+:Author: Michael Foord
+:Contact: fuzzyman@voidspace.org.uk
+:Version: 0.1.0
+:Date: 2005/09/07
+:License: `BSD License`_ [#]_
+:Online Version: `ConfigPersist online`_
+
+.. _`configpersist online`: http://www.voidspace.org.uk/python/configpersist.html
+.. _BSD License: BSD-LICENSE.txt
+
+.. contents:: Data Persistence
+
+Introduction
+============
+
+This module contains various functions for data persistence with ConfigObj_.
+
+ConfigObj_ is a pure python module for the easy reading and writing of
+application configuration data. It uses an *ini* file like syntax - similar to
+the ConfigParser_ module - but with much greater power.
+
+ConfigObj in conjunction with validate_ can store nested sections (like
+dictionaries) - with values as integers, floats, strings, booleans, and lists
+of these values. This makes it ideal for certain types of human readable (and
+writeable) data persistence.
+
+For a discussion of this idea (out of which this module was born) - see
+`ConfigObj for Data Persistence`_.
+
+You can find ConfigObj, and other useful modules, over at the
+`Voidspace Modules Page`_.
+
+.. _Voidspace Modules Page: /python/modules.shtml
+.. _validate: /python/validate.html
+.. _ConfigParser: http://docs.python.org/lib/module-configparser.html
+.. _ConfigObj for Data Persistence: /python/articles/configobj_for_data_persistence.shtml
+.. _ConfigObj: /python/configobj.html
+
+Downloading
+===========
+
+You can download the ConfigPersist module from : ConfigPersist.py_
+
+.. _ConfigPersist.py: /cgi-bin/voidspace/downman.py?file=ConfigPersist.py
+
+Limitations
+===========
+
+.. hint::
+
+ This is an extract from the `ConfigObj for Data Persistence`_ article.
+
+ConfigObj can't just be used to represent arbitrary data structures - even if
+all the members are allowed types.
+
+ * Although dictionaries can be nested, they can't be inside lists.
+ * Lists also can't be nested inside each other [#]_.
+ * Values other than strings need a schema (a ``configspec``) to convert
+ them back into the right type.
+ * Dictionary keys must be strings.
+ * It is actually impossible to store a string containing single triple
+ quotes (``'''``) *and* double triple quotes (``"""``).
+ * List members cannot contain carriage returns. (Single line values only). [#]_
+
+ConfigObj *isn't* a data persistence module - this list of restrictions tells
+you that much. However if you examine the typical data structures used in your
+programs you may find that these restrictions aren't a problem for many of them.
+
+So Why Do It ?
+--------------
+
+Why would we want to do this ? Well, the usual method for preserving data
+structures is the Python pickle_ module. This can store and retrieve a much
+wider range of objects - with *none* of the restrictions above.
+
+However :
+
+ * Pickles aren't human readable or writeable. This makes ConfigObj ideal
+ for debugging or where you want to manually modify the data.
+ * Pickles are unsafe - a maliciously crafted pickle can cause arbitrary
+ code execution.
+ * ConfigObj is slightly easier to use - ``data = ConfigObj(filename)`` and
+ ``data.write()``.
+
+Of these three reasons the first is overwhelmingly the most compelling.
+
+.. _pickle: http://docs.python.org/lib/module-pickle.html
+
+The Functions
+=============
+
+The first three functions provide the highest level interface to this module.
+You can use these without needing to the other functions.
+
+save_configspec_ could be useful to anyone using the ConfigObj module - not
+just for data persistence.
+
+
+store
+-----
+
+::
+
+ store(config)
+
+
+Passed a ConfigObj instance add type info and save.
+
+Returns the result of calling ``config.write()``.
+
+.. caution::
+
+ This function modifies the ConfigObj instance by adding the ``__types__``
+ data.
+
+ You can call typeinfo_to_configspec_ to reverse this.
+
+
+restore
+-------
+
+::
+
+ restore(stored)
+
+Restore a ConfigObj saved using the ``store`` function.
+
+Takes a filename or list of lines, returns the ConfigObj instance.
+
+Uses the built-in ``Validator`` instance of this module (vtor).
+
+Raises an ``ImportError`` if the validate module isn't available.
+
+
+save_configspec
+---------------
+
+::
+
+ save_configspec(config)
+
+Creates a configspec for a ConfigObj (which must be comprised of the basic
+datatypes) and returns it as a list of lines.
+
+Lower Level Functions
+---------------------
+
+These functions provide a slightly lower level interface to adding type info to
+a ConfigObj. They are still very easy to use though.
+
+add_configspec
+~~~~~~~~~~~~~~
+
+::
+
+ add_configspec(config)
+
+A function that adds a configspec to a ConfigObj.
+
+Will only work for ConfigObj instances using basic datatypes :
+
+ * floats
+ * strings
+ * ints
+ * booleans
+ * Lists of the above
+
+write_configspec
+~~~~~~~~~~~~~~~~
+
+::
+
+ write_configspec(config)
+
+Return the configspec (of a ConfigObj) as a list of lines.
+
+You must first call ``add_configspec``. You can use save_configspec_ which does
+both in one step.
+
+add_typeinfo
+~~~~~~~~~~~~
+
+::
+
+ add_typeinfo(config)
+
+Turns the configspec attribute of each section into a member of the
+section. (Called ``__types__``).
+
+You must have already called ``add_configspec`` on the ConfigObj.
+
+typeinfo_to_configspec
+~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ typeinfo_to_configspec(config)
+
+Turns the ``__types__`` member of each section into a configspec.
+
+(The opposite of ``add_typeinfo``).
+
+vtor
+~~~~
+
+This object isn't actually a function - it's the ``Validator`` instance used
+by this module.
+
+If the validate module isn't available - this object will be ``None``.
+
+
+CHANGELOG
+=========
+
+See the source code for CHANGELOG (and TODO/ISSUES).
+
+Footnotes
+=========
+
+.. [#] Online at http://www.voidspace.org.uk/python/license.shtml
+.. [#] We could remove this restriction by using the listquote_ module to parse
+ ConfigObj values. Unfortunately a bug in ConfigObj means that this is
+ currently not possible.
+.. [#] List members can also not contain both types of quote. We could remove
+ these last two restrictions using the ``quote_unescape`` function from
+ listquote - it's a bit ungainly though. Note however that the ``walk``
+ method of ConfigObj is ideal for transforming values in this way.
+ It will recursively walk the values and apply a function to them all.
+
+.. _listquote: /python/listquote.html
+
+.. raw:: html
+
+ <center>
+ <a href="http://sourceforge.net/donate/index.php?group_id=123265">
+ <img src="http://images.sourceforge.net/images/project-support.jpg" width="88" height="32" border="0" alt="Support This Project" />
+ </a>
+ <a href="http://sourceforge.net">
+ <img src="http://sourceforge.net/sflogo.php?group_id=123265&amp;type=1" width="88" height="31" border="0" alt="SourceForge.net Logo" />
+ </a>
+ <br />
+ <a href="http://www.python.org">
+ <img src="images/powered_by_python.jpg" width="602" height="186" border="0" />
+ </a>
+ <a href="http://www.opensource.org">
+ <img src="images/osi-certified-120x100.gif" width="120" height="100" border="0" />
+ <br /><strong>Certified Open Source</strong>
+ </a>
+ <br /><br />
+ <script type="text/javascript" language="JavaScript">var site="s16atlantibots"</script>
+ <script type="text/javascript" language="JavaScript1.2" src="http://s16.sitemeter.com/js/counter.js?site=s16atlantibots"></script>
+ <noscript>
+ <a href="http://s16.sitemeter.com/stats.asp?site=s16atlantibots">
+ <img src="http://s16.sitemeter.com/meter.asp?site=s16atlantibots" alt="Site Meter" border=0 />
+ </a>
+ </noscript>
+ <br />
+ </center>
+
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..7170656
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,48 @@
+# setup.py
+# Install script for ConfigObj
+# Copyright (C) 2005 Michael Foord, Mark Andrews, Nicola Larosa
+# E-mail: fuzzyman AT voidspace DOT org DOT uk
+# mark AT la-la DOT com
+# nico AT tekNico DOT net
+
+# This software is licensed under the terms of the BSD license.
+# http://www.voidspace.org.uk/python/license.shtml
+# Basically you're free to copy, modify, distribute and relicense it,
+# So long as you keep a copy of the license with it.
+
+# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
+# For information about bugfixes, updates and support, please join the
+# Rest2Web mailing list:
+# http://lists.sourceforge.net/lists/listinfo/rest2web-develop
+# Comments, suggestions and bug reports welcome.
+"""
+**setup.py** for ``configobj`` and ``validate`` modules.
+"""
+
+if __name__ == '__main__':
+ import sys
+ from distutils.core import setup
+ from configobj import __version__ as VERSION
+
+ NAME = 'configobj'
+ MODULES = 'configobj', 'validate'
+ DESCRIPTION = 'Config file reading, writing, and validation.'
+ URL = 'http://www.voidspace.org.uk/python/configobj.html'
+ LICENSE = 'BSD'
+ PLATFORMS = ["Platform Independent"]
+
+ if sys.version < '2.2.3':
+ from distutils.dist import DistributionMetadata
+ DistributionMetadata.classifiers = None
+ DistributionMetadata.download_url = None
+
+ setup(name= NAME,
+ version= VERSION,
+ description= DESCRIPTION,
+ license = LICENSE,
+ platforms = PLATFORMS,
+ author= 'Michael Foord & Nicola Larosa',
+ author_email= 'fuzzyman@voidspace.org.uk',
+ url= URL,
+ py_modules = MODULES,
+ )
diff --git a/test_configobj.py b/test_configobj.py
new file mode 100644
index 0000000..e5b1687
--- /dev/null
+++ b/test_configobj.py
@@ -0,0 +1,2085 @@
+# configobj_test.py
+# doctests for ConfigObj
+# A config file reader/writer that supports nested sections in config files.
+# Copyright (C) 2005-2008 Michael Foord, Nicola Larosa
+# E-mail: fuzzyman AT voidspace DOT org DOT uk
+# nico AT tekNico DOT net
+
+# ConfigObj 4
+# http://www.voidspace.org.uk/python/configobj.html
+
+# Released subject to the BSD License
+# Please see http://www.voidspace.org.uk/python/license.shtml
+
+# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
+# For information about bugfixes, updates and support, please join the
+# ConfigObj mailing list:
+# http://lists.sourceforge.net/lists/listinfo/configobj-develop
+# Comments, suggestions and bug reports welcome.
+
+
+from __future__ import generators
+from StringIO import StringIO
+
+import os
+import sys
+INTP_VER = sys.version_info[:2]
+if INTP_VER < (2, 2):
+ raise RuntimeError("Python v.2.2 or later needed")
+
+try:
+ from codecs import BOM_UTF8
+except ImportError:
+ # Python 2.2 does not have this
+ # UTF-8
+ BOM_UTF8 = '\xef\xbb\xbf'
+
+from configobj import *
+from validate import Validator, VdtValueTooSmallError
+
+
+"""
+ >>> z = ConfigObj()
+ >>> z['a'] = 'a'
+ >>> z['sect'] = {
+ ... 'subsect': {
+ ... 'a': 'fish',
+ ... 'b': 'wobble',
+ ... },
+ ... 'member': 'value',
+ ... }
+ >>> x = ConfigObj(z.write())
+ >>> z == x
+ 1
+"""
+
+
+def _error_test():
+ """
+ Testing the error classes.
+
+ >>> raise ConfigObjError
+ Traceback (most recent call last):
+ ConfigObjError
+
+ >>> raise NestingError
+ Traceback (most recent call last):
+ NestingError
+
+ >>> raise ParseError
+ Traceback (most recent call last):
+ ParseError
+
+ >>> raise DuplicateError
+ Traceback (most recent call last):
+ DuplicateError
+
+ >>> raise ConfigspecError
+ Traceback (most recent call last):
+ ConfigspecError
+
+ >>> raise InterpolationLoopError('yoda')
+ Traceback (most recent call last):
+ InterpolationLoopError: interpolation loop detected in value "yoda".
+
+ >>> raise RepeatSectionError
+ Traceback (most recent call last):
+ RepeatSectionError
+
+ >>> raise MissingInterpolationOption('yoda')
+ Traceback (most recent call last):
+ MissingInterpolationOption: missing option "yoda" in interpolation.
+
+
+ >>> raise ReloadError()
+ Traceback (most recent call last):
+ ReloadError: reload failed, filename is not set.
+ >>> try:
+ ... raise ReloadError()
+ ... except IOError:
+ ... pass
+ ... else:
+ ... raise Exception('We should catch a ReloadError as an IOError')
+ >>>
+
+ """
+
+
+def _section_test():
+ """
+ Tests from Section methods.
+
+ >>> n = a.dict()
+ >>> n == a
+ 1
+ >>> n is a
+ 0
+
+ >>> a = '''[section1]
+ ... option1 = True
+ ... [[subsection]]
+ ... more_options = False
+ ... # end of file'''.splitlines()
+ >>> b = '''# File is user.ini
+ ... [section1]
+ ... option1 = False
+ ... # end of file'''.splitlines()
+ >>> c1 = ConfigObj(b)
+ >>> c2 = ConfigObj(a)
+ >>> c2.merge(c1)
+ >>> c2
+ ConfigObj({'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}})
+
+ >>> config = '''[XXXXsection]
+ ... XXXXkey = XXXXvalue'''.splitlines()
+ >>> cfg = ConfigObj(config)
+ >>> cfg
+ ConfigObj({'XXXXsection': {'XXXXkey': 'XXXXvalue'}})
+ >>> def transform(section, key):
+ ... val = section[key]
+ ... newkey = key.replace('XXXX', 'CLIENT1')
+ ... section.rename(key, newkey)
+ ... if isinstance(val, (tuple, list, dict)):
+ ... pass
+ ... else:
+ ... val = val.replace('XXXX', 'CLIENT1')
+ ... section[newkey] = val
+ >>> cfg.walk(transform, call_on_sections=True)
+ {'CLIENT1section': {'CLIENT1key': None}}
+ >>> cfg
+ ConfigObj({'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}})
+ """
+
+
+def _test_reset():
+ """
+ >>> something = object()
+ >>> c = ConfigObj()
+ >>> c['something'] = something
+ >>> c['section'] = {'something': something}
+ >>> c.filename = 'fish'
+ >>> c.raise_errors = something
+ >>> c.list_values = something
+ >>> c.create_empty = something
+ >>> c.file_error = something
+ >>> c.stringify = something
+ >>> c.indent_type = something
+ >>> c.encoding = something
+ >>> c.default_encoding = something
+ >>> c.BOM = something
+ >>> c.newlines = something
+ >>> c.write_empty_values = something
+ >>> c.unrepr = something
+ >>> c.initial_comment = something
+ >>> c.final_comment = something
+ >>> c.configspec = something
+ >>> c.inline_comments = something
+ >>> c.comments = something
+ >>> c.defaults = something
+ >>> c.default_values = something
+ >>> c.reset()
+ >>>
+ >>> c.filename
+ >>> c.raise_errors
+ False
+ >>> c.list_values
+ True
+ >>> c.create_empty
+ False
+ >>> c.file_error
+ False
+ >>> c.interpolation
+ True
+ >>> c.configspec
+ >>> c.stringify
+ True
+ >>> c.indent_type
+ >>> c.encoding
+ >>> c.default_encoding
+ >>> c.unrepr
+ False
+ >>> c.write_empty_values
+ False
+ >>> c.inline_comments
+ {}
+ >>> c.comments
+ {}
+ >>> c.defaults
+ []
+ >>> c.default_values
+ {}
+ >>> c == ConfigObj()
+ True
+ >>> c
+ ConfigObj({})
+ """
+
+
+def _test_reload():
+ """
+ >>> c = ConfigObj(StringIO())
+ >>> c.reload()
+ Traceback (most recent call last):
+ ReloadError: reload failed, filename is not set.
+ >>> c = ConfigObj()
+ >>> c.reload()
+ Traceback (most recent call last):
+ ReloadError: reload failed, filename is not set.
+ >>> c = ConfigObj([])
+ >>> c.reload()
+ Traceback (most recent call last):
+ ReloadError: reload failed, filename is not set.
+
+ We need to use a real file as reload is only for files loaded from
+ the filesystem.
+ >>> h = open('temp', 'w')
+ >>> h.write('''
+ ... test1=40
+ ... test2=hello
+ ... test3=3
+ ... test4=5.0
+ ... [section]
+ ... test1=40
+ ... test2=hello
+ ... test3=3
+ ... test4=5.0
+ ... [[sub section]]
+ ... test1=40
+ ... test2=hello
+ ... test3=3
+ ... test4=5.0
+ ... [section2]
+ ... test1=40
+ ... test2=hello
+ ... test3=3
+ ... test4=5.0
+ ... ''')
+ >>> h.close()
+ >>> configspec = '''
+ ... test1= integer(30,50)
+ ... test2= string
+ ... test3=integer
+ ... test4=float(4.5)
+ ... [section]
+ ... test1=integer(30,50)
+ ... test2=string
+ ... test3=integer
+ ... test4=float(4.5)
+ ... [[sub section]]
+ ... test1=integer(30,50)
+ ... test2=string
+ ... test3=integer
+ ... test4=float(4.5)
+ ... [section2]
+ ... test1=integer(30,50)
+ ... test2=string
+ ... test3=integer
+ ... test4=float(4.5)
+ ... '''.split('\\n')
+ >>> c = ConfigObj('temp', configspec=configspec)
+ >>> c.configspec['test1'] = 'integer(50,60)'
+ >>> backup = ConfigObj('temp')
+ >>> del c['section']
+ >>> del c['test1']
+ >>> c['extra'] = '3'
+ >>> c['section2']['extra'] = '3'
+ >>> c.reload()
+ >>> c == backup
+ True
+ >>> c.validate(Validator())
+ True
+ >>> os.remove('temp')
+ """
+
+
+def _doctest():
+ """
+ Dummy function to hold some of the doctests.
+
+ >>> a.depth
+ 0
+ >>> a == {
+ ... 'key2': 'val',
+ ... 'key1': 'val',
+ ... 'lev1c': {
+ ... 'lev2c': {
+ ... 'lev3c': {
+ ... 'key1': 'val',
+ ... },
+ ... },
+ ... },
+ ... 'lev1b': {
+ ... 'key2': 'val',
+ ... 'key1': 'val',
+ ... 'lev2ba': {
+ ... 'key1': 'val',
+ ... },
+ ... 'lev2bb': {
+ ... 'key1': 'val',
+ ... },
+ ... },
+ ... 'lev1a': {
+ ... 'key2': 'val',
+ ... 'key1': 'val',
+ ... },
+ ... }
+ 1
+ >>> b.depth
+ 0
+ >>> b == {
+ ... 'key3': 'val3',
+ ... 'key2': 'val2',
+ ... 'key1': 'val1',
+ ... 'section 1': {
+ ... 'keys11': 'val1',
+ ... 'keys13': 'val3',
+ ... 'keys12': 'val2',
+ ... },
+ ... 'section 2': {
+ ... 'section 2 sub 1': {
+ ... 'fish': '3',
+ ... },
+ ... 'keys21': 'val1',
+ ... 'keys22': 'val2',
+ ... 'keys23': 'val3',
+ ... },
+ ... }
+ 1
+ >>> t = '''
+ ... 'a' = b # !"$%^&*(),::;'@~#= 33
+ ... "b" = b #= 6, 33
+ ... ''' .split('\\n')
+ >>> t2 = ConfigObj(t)
+ >>> assert t2 == {'a': 'b', 'b': 'b'}
+ >>> t2.inline_comments['b'] = ''
+ >>> del t2['a']
+ >>> assert t2.write() == ['','b = b', '']
+
+ # Test ``list_values=False`` stuff
+ >>> c = '''
+ ... key1 = no quotes
+ ... key2 = 'single quotes'
+ ... key3 = "double quotes"
+ ... key4 = "list", 'with', several, "quotes"
+ ... '''
+ >>> cfg = ConfigObj(c.splitlines(), list_values=False)
+ >>> cfg == {'key1': 'no quotes', 'key2': "'single quotes'",
+ ... 'key3': '"double quotes"',
+ ... 'key4': '"list", \\'with\\', several, "quotes"'
+ ... }
+ 1
+ >>> cfg = ConfigObj(list_values=False)
+ >>> cfg['key1'] = 'Multiline\\nValue'
+ >>> cfg['key2'] = '''"Value" with 'quotes' !'''
+ >>> cfg.write()
+ ["key1 = '''Multiline\\nValue'''", 'key2 = "Value" with \\'quotes\\' !']
+ >>> cfg.list_values = True
+ >>> cfg.write() == ["key1 = '''Multiline\\nValue'''",
+ ... 'key2 = \\'\\'\\'"Value" with \\'quotes\\' !\\'\\'\\'']
+ 1
+
+ Test flatten_errors:
+
+ >>> config = '''
+ ... test1=40
+ ... test2=hello
+ ... test3=3
+ ... test4=5.0
+ ... [section]
+ ... test1=40
+ ... test2=hello
+ ... test3=3
+ ... test4=5.0
+ ... [[sub section]]
+ ... test1=40
+ ... test2=hello
+ ... test3=3
+ ... test4=5.0
+ ... '''.split('\\n')
+ >>> configspec = '''
+ ... test1= integer(30,50)
+ ... test2= string
+ ... test3=integer
+ ... test4=float(6.0)
+ ... [section]
+ ... test1=integer(30,50)
+ ... test2=string
+ ... test3=integer
+ ... test4=float(6.0)
+ ... [[sub section]]
+ ... test1=integer(30,50)
+ ... test2=string
+ ... test3=integer
+ ... test4=float(6.0)
+ ... '''.split('\\n')
+ >>> val = Validator()
+ >>> c1 = ConfigObj(config, configspec=configspec)
+ >>> res = c1.validate(val)
+ >>> flatten_errors(c1, res) == [([], 'test4', False), (['section',
+ ... 'sub section'], 'test4', False), (['section'], 'test4', False)]
+ True
+ >>> res = c1.validate(val, preserve_errors=True)
+ >>> check = flatten_errors(c1, res)
+ >>> check[0][:2]
+ ([], 'test4')
+ >>> check[1][:2]
+ (['section', 'sub section'], 'test4')
+ >>> check[2][:2]
+ (['section'], 'test4')
+ >>> for entry in check:
+ ... isinstance(entry[2], VdtValueTooSmallError)
+ ... print str(entry[2])
+ True
+ the value "5.0" is too small.
+ True
+ the value "5.0" is too small.
+ True
+ the value "5.0" is too small.
+
+ Test unicode handling, BOM, write witha file like object and line endings :
+ >>> u_base = '''
+ ... # initial comment
+ ... # inital comment 2
+ ...
+ ... test1 = some value
+ ... # comment
+ ... test2 = another value # inline comment
+ ... # section comment
+ ... [section] # inline comment
+ ... test = test # another inline comment
+ ... test2 = test2
+ ...
+ ... # final comment
+ ... # final comment2
+ ... '''
+ >>> u = u_base.encode('utf_8').splitlines(True)
+ >>> u[0] = BOM_UTF8 + u[0]
+ >>> uc = ConfigObj(u)
+ >>> uc.encoding = None
+ >>> uc.BOM == True
+ 1
+ >>> uc == {'test1': 'some value', 'test2': 'another value',
+ ... 'section': {'test': 'test', 'test2': 'test2'}}
+ 1
+ >>> uc = ConfigObj(u, encoding='utf_8', default_encoding='latin-1')
+ >>> uc.BOM
+ 1
+ >>> isinstance(uc['test1'], unicode)
+ 1
+ >>> uc.encoding
+ 'utf_8'
+ >>> uc.newlines
+ '\\n'
+ >>> uc['latin1'] = "This costs lot's of "
+ >>> a_list = uc.write()
+ >>> len(a_list)
+ 15
+ >>> isinstance(a_list[0], str)
+ 1
+ >>> a_list[0].startswith(BOM_UTF8)
+ 1
+ >>> u = u_base.replace('\\n', '\\r\\n').encode('utf_8').splitlines(True)
+ >>> uc = ConfigObj(u)
+ >>> uc.newlines
+ '\\r\\n'
+ >>> uc.newlines = '\\r'
+ >>> file_like = StringIO()
+ >>> uc.write(file_like)
+ >>> file_like.seek(0)
+ >>> uc2 = ConfigObj(file_like)
+ >>> uc2 == uc
+ 1
+ >>> uc2.filename == None
+ 1
+ >>> uc2.newlines == '\\r'
+ 1
+
+ Test validate in copy mode
+ >>> a = '''
+ ... # Initial Comment
+ ...
+ ... key1 = string(default=Hello)
+ ...
+ ... # section comment
+ ... [section] # inline comment
+ ... # key1 comment
+ ... key1 = integer(default=6)
+ ... # key2 comment
+ ... key2 = boolean(default=True)
+ ...
+ ... # subsection comment
+ ... [[sub-section]] # inline comment
+ ... # another key1 comment
+ ... key1 = float(default=3.0)'''.splitlines()
+ >>> b = ConfigObj(configspec=a)
+ >>> b.validate(val, copy=True)
+ 1
+ >>> b.write() == ['',
+ ... '# Initial Comment',
+ ... '',
+ ... 'key1 = Hello',
+ ... '',
+ ... '# section comment',
+ ... '[section] # inline comment',
+ ... ' # key1 comment',
+ ... ' key1 = 6',
+ ... ' # key2 comment',
+ ... ' key2 = True',
+ ... ' ',
+ ... ' # subsection comment',
+ ... ' [[sub-section]] # inline comment',
+ ... ' # another key1 comment',
+ ... ' key1 = 3.0']
+ 1
+
+ Test Writing Empty Values
+ >>> a = '''
+ ... key1 =
+ ... key2 =# a comment'''
+ >>> b = ConfigObj(a.splitlines())
+ >>> b.write()
+ ['', 'key1 = ""', 'key2 = "" # a comment']
+ >>> b.write_empty_values = True
+ >>> b.write()
+ ['', 'key1 = ', 'key2 = # a comment']
+
+ Test unrepr when reading
+ >>> a = '''
+ ... key1 = (1, 2, 3) # comment
+ ... key2 = True
+ ... key3 = 'a string'
+ ... key4 = [1, 2, 3, 'a mixed list']
+ ... '''.splitlines()
+ >>> b = ConfigObj(a, unrepr=True)
+ >>> b == {'key1': (1, 2, 3),
+ ... 'key2': True,
+ ... 'key3': 'a string',
+ ... 'key4': [1, 2, 3, 'a mixed list']}
+ 1
+
+ Test unrepr when writing
+ >>> c = ConfigObj(b.write(), unrepr=True)
+ >>> c == b
+ 1
+
+ Test unrepr with multiline values
+ >>> a = '''k = \"""{
+ ... 'k1': 3,
+ ... 'k2': 6.0}\"""
+ ... '''.splitlines()
+ >>> c = ConfigObj(a, unrepr=True)
+ >>> c == {'k': {'k1': 3, 'k2': 6.0}}
+ 1
+
+ Test unrepr with a dictionary
+ >>> a = 'k = {"a": 1}'.splitlines()
+ >>> c = ConfigObj(a, unrepr=True)
+ >>> type(c['k']) == dict
+ 1
+
+ >>> a = ConfigObj()
+ >>> a['a'] = 'fish'
+ >>> a.as_bool('a')
+ Traceback (most recent call last):
+ ValueError: Value "fish" is neither True nor False
+ >>> a['b'] = 'True'
+ >>> a.as_bool('b')
+ 1
+ >>> a['b'] = 'off'
+ >>> a.as_bool('b')
+ 0
+
+ >>> a = ConfigObj()
+ >>> a['a'] = 'fish'
+ >>> try:
+ ... a.as_int('a') #doctest: +ELLIPSIS
+ ... except ValueError, e:
+ ... err_mess = str(e)
+ >>> err_mess.startswith('invalid literal for int()')
+ 1
+ >>> a['b'] = '1'
+ >>> a.as_int('b')
+ 1
+ >>> a['b'] = '3.2'
+ >>> try:
+ ... a.as_int('b') #doctest: +ELLIPSIS
+ ... except ValueError, e:
+ ... err_mess = str(e)
+ >>> err_mess.startswith('invalid literal for int()')
+ 1
+
+ >>> a = ConfigObj()
+ >>> a['a'] = 'fish'
+ >>> a.as_float('a')
+ Traceback (most recent call last):
+ ValueError: invalid literal for float(): fish
+ >>> a['b'] = '1'
+ >>> a.as_float('b')
+ 1.0
+ >>> a['b'] = '3.2'
+ >>> a.as_float('b')
+ 3.2000000000000002
+
+ Test # with unrepr
+ >>> a = '''
+ ... key1 = (1, 2, 3) # comment
+ ... key2 = True
+ ... key3 = 'a string'
+ ... key4 = [1, 2, 3, 'a mixed list#']
+ ... '''.splitlines()
+ >>> b = ConfigObj(a, unrepr=True)
+ >>> b == {'key1': (1, 2, 3),
+ ... 'key2': True,
+ ... 'key3': 'a string',
+ ... 'key4': [1, 2, 3, 'a mixed list#']}
+ 1
+ """
+
+ # Comments are no longer parsed from values in configspecs
+ # so the following test fails and is disabled
+ untested = """
+ Test validate in copy mode
+ >>> a = '''
+ ... # Initial Comment
+ ...
+ ... key1 = string(default=Hello) # comment 1
+ ...
+ ... # section comment
+ ... [section] # inline comment
+ ... # key1 comment
+ ... key1 = integer(default=6) # an integer value
+ ... # key2 comment
+ ... key2 = boolean(default=True) # a boolean
+ ...
+ ... # subsection comment
+ ... [[sub-section]] # inline comment
+ ... # another key1 comment
+ ... key1 = float(default=3.0) # a float'''.splitlines()
+ >>> b = ConfigObj(configspec=a)
+ >>> b.validate(val, copy=True)
+ 1
+ >>> b.write()
+ >>> b.write() == ['',
+ ... '# Initial Comment',
+ ... '',
+ ... 'key1 = Hello # comment 1',
+ ... '',
+ ... '# section comment',
+ ... '[section] # inline comment',
+ ... ' # key1 comment',
+ ... ' key1 = 6 # an integer value',
+ ... ' # key2 comment',
+ ... ' key2 = True # a boolean',
+ ... ' ',
+ ... ' # subsection comment',
+ ... ' [[sub-section]] # inline comment',
+ ... ' # another key1 comment',
+ ... ' key1 = 3.0 # a float']
+ 1
+ """
+
+
+def _test_configobj():
+ """
+ Testing ConfigObj
+ Testing with duplicate keys and sections.
+
+ >>> c = '''
+ ... [hello]
+ ... member = value
+ ... [hello again]
+ ... member = value
+ ... [ "hello" ]
+ ... member = value
+ ... '''
+ >>> ConfigObj(c.split('\\n'), raise_errors = True)
+ Traceback (most recent call last):
+ DuplicateError: Duplicate section name at line 6.
+
+ >>> d = '''
+ ... [hello]
+ ... member = value
+ ... [hello again]
+ ... member1 = value
+ ... member2 = value
+ ... 'member1' = value
+ ... [ "and again" ]
+ ... member = value
+ ... '''
+ >>> ConfigObj(d.split('\\n'), raise_errors = True)
+ Traceback (most recent call last):
+ DuplicateError: Duplicate keyword name at line 7.
+
+ Testing ConfigParser-style interpolation
+
+ >>> c = ConfigObj()
+ >>> c['DEFAULT'] = {
+ ... 'b': 'goodbye',
+ ... 'userdir': 'c:\\\\home',
+ ... 'c': '%(d)s',
+ ... 'd': '%(c)s'
+ ... }
+ >>> c['section'] = {
+ ... 'a': '%(datadir)s\\\\some path\\\\file.py',
+ ... 'b': '%(userdir)s\\\\some path\\\\file.py',
+ ... 'c': 'Yo %(a)s',
+ ... 'd': '%(not_here)s',
+ ... 'e': '%(e)s',
+ ... }
+ >>> c['section']['DEFAULT'] = {
+ ... 'datadir': 'c:\\\\silly_test',
+ ... 'a': 'hello - %(b)s',
+ ... }
+ >>> c['section']['a'] == 'c:\\\\silly_test\\\\some path\\\\file.py'
+ 1
+ >>> c['section']['b'] == 'c:\\\\home\\\\some path\\\\file.py'
+ 1
+ >>> c['section']['c'] == 'Yo c:\\\\silly_test\\\\some path\\\\file.py'
+ 1
+
+ Switching Interpolation Off
+
+ >>> c.interpolation = False
+ >>> c['section']['a'] == '%(datadir)s\\\\some path\\\\file.py'
+ 1
+ >>> c['section']['b'] == '%(userdir)s\\\\some path\\\\file.py'
+ 1
+ >>> c['section']['c'] == 'Yo %(a)s'
+ 1
+
+ Testing the interpolation errors.
+
+ >>> c.interpolation = True
+ >>> c['section']['d']
+ Traceback (most recent call last):
+ MissingInterpolationOption: missing option "not_here" in interpolation.
+ >>> c['section']['e']
+ Traceback (most recent call last):
+ InterpolationLoopError: interpolation loop detected in value "e".
+
+ Testing Template-style interpolation
+
+ >>> interp_cfg = '''
+ ... [DEFAULT]
+ ... keyword1 = value1
+ ... 'keyword 2' = 'value 2'
+ ... reference = ${keyword1}
+ ... foo = 123
+ ...
+ ... [ section ]
+ ... templatebare = $keyword1/foo
+ ... bar = $$foo
+ ... dollar = $$300.00
+ ... stophere = $$notinterpolated
+ ... with_braces = ${keyword1}s (plural)
+ ... with_spaces = ${keyword 2}!!!
+ ... with_several = $keyword1/$reference/$keyword1
+ ... configparsersample = %(keyword 2)sconfig
+ ... deep = ${reference}
+ ...
+ ... [[DEFAULT]]
+ ... baz = $foo
+ ...
+ ... [[ sub-section ]]
+ ... quux = '$baz + $bar + $foo'
+ ...
+ ... [[[ sub-sub-section ]]]
+ ... convoluted = "$bar + $baz + $quux + $bar"
+ ... '''
+ >>> c = ConfigObj(interp_cfg.split('\\n'), interpolation='Template')
+ >>> c['section']['templatebare']
+ 'value1/foo'
+ >>> c['section']['dollar']
+ '$300.00'
+ >>> c['section']['stophere']
+ '$notinterpolated'
+ >>> c['section']['with_braces']
+ 'value1s (plural)'
+ >>> c['section']['with_spaces']
+ 'value 2!!!'
+ >>> c['section']['with_several']
+ 'value1/value1/value1'
+ >>> c['section']['configparsersample']
+ '%(keyword 2)sconfig'
+ >>> c['section']['deep']
+ 'value1'
+ >>> c['section']['sub-section']['quux']
+ '123 + $foo + 123'
+ >>> c['section']['sub-section']['sub-sub-section']['convoluted']
+ '$foo + 123 + 123 + $foo + 123 + $foo'
+
+ Testing our quoting.
+
+ >>> i._quote('\"""\\'\\'\\'')
+ Traceback (most recent call last):
+ ConfigObjError: Value \"\"""'''" cannot be safely quoted.
+ >>> try:
+ ... i._quote('\\n', multiline=False)
+ ... except ConfigObjError, e:
+ ... e.msg
+ 'Value "\\n" cannot be safely quoted.'
+ >>> i._quote(' "\\' ', multiline=False)
+ Traceback (most recent call last):
+ ConfigObjError: Value " "' " cannot be safely quoted.
+
+ Testing with "stringify" off.
+ >>> c.stringify = False
+ >>> c['test'] = 1
+ Traceback (most recent call last):
+ TypeError: Value is not a string "1".
+
+ Testing Empty values.
+ >>> cfg_with_empty = '''
+ ... k =
+ ... k2 =# comment test
+ ... val = test
+ ... val2 = ,
+ ... val3 = 1,
+ ... val4 = 1, 2
+ ... val5 = 1, 2, \'''.splitlines()
+ >>> cwe = ConfigObj(cfg_with_empty)
+ >>> cwe == {'k': '', 'k2': '', 'val': 'test', 'val2': [],
+ ... 'val3': ['1'], 'val4': ['1', '2'], 'val5': ['1', '2']}
+ 1
+ >>> cwe = ConfigObj(cfg_with_empty, list_values=False)
+ >>> cwe == {'k': '', 'k2': '', 'val': 'test', 'val2': ',',
+ ... 'val3': '1,', 'val4': '1, 2', 'val5': '1, 2,'}
+ 1
+
+ Testing list values.
+ >>> testconfig3 = \'''
+ ... a = ,
+ ... b = test,
+ ... c = test1, test2 , test3
+ ... d = test1, test2, test3,
+ ... \'''
+ >>> d = ConfigObj(testconfig3.split('\\n'), raise_errors=True)
+ >>> d['a'] == []
+ 1
+ >>> d['b'] == ['test']
+ 1
+ >>> d['c'] == ['test1', 'test2', 'test3']
+ 1
+ >>> d['d'] == ['test1', 'test2', 'test3']
+ 1
+
+ Testing with list values off.
+
+ >>> e = ConfigObj(
+ ... testconfig3.split('\\n'),
+ ... raise_errors=True,
+ ... list_values=False)
+ >>> e['a'] == ','
+ 1
+ >>> e['b'] == 'test,'
+ 1
+ >>> e['c'] == 'test1, test2 , test3'
+ 1
+ >>> e['d'] == 'test1, test2, test3,'
+ 1
+
+ Testing creating from a dictionary.
+
+ >>> f = {
+ ... 'key1': 'val1',
+ ... 'key2': 'val2',
+ ... 'section 1': {
+ ... 'key1': 'val1',
+ ... 'key2': 'val2',
+ ... 'section 1b': {
+ ... 'key1': 'val1',
+ ... 'key2': 'val2',
+ ... },
+ ... },
+ ... 'section 2': {
+ ... 'key1': 'val1',
+ ... 'key2': 'val2',
+ ... 'section 2b': {
+ ... 'key1': 'val1',
+ ... 'key2': 'val2',
+ ... },
+ ... },
+ ... 'key3': 'val3',
+ ... }
+ >>> g = ConfigObj(f)
+ >>> f == g
+ 1
+
+ Testing we correctly detect badly built list values (4 of them).
+
+ >>> testconfig4 = '''
+ ... config = 3,4,,
+ ... test = 3,,4
+ ... fish = ,,
+ ... dummy = ,,hello, goodbye
+ ... '''
+ >>> try:
+ ... ConfigObj(testconfig4.split('\\n'))
+ ... except ConfigObjError, e:
+ ... len(e.errors)
+ 4
+
+ Testing we correctly detect badly quoted values (4 of them).
+
+ >>> testconfig5 = '''
+ ... config = "hello # comment
+ ... test = 'goodbye
+ ... fish = 'goodbye # comment
+ ... dummy = "hello again
+ ... '''
+ >>> try:
+ ... ConfigObj(testconfig5.split('\\n'))
+ ... except ConfigObjError, e:
+ ... len(e.errors)
+ 4
+
+ Test Multiline Comments
+ >>> i == {
+ ... 'name4': ' another single line value ',
+ ... 'multi section': {
+ ... 'name4': '\\n Well, this is a\\n multiline '
+ ... 'value\\n ',
+ ... 'name2': '\\n Well, this is a\\n multiline '
+ ... 'value\\n ',
+ ... 'name3': '\\n Well, this is a\\n multiline '
+ ... 'value\\n ',
+ ... 'name1': '\\n Well, this is a\\n multiline '
+ ... 'value\\n ',
+ ... },
+ ... 'name2': ' another single line value ',
+ ... 'name3': ' a single line value ',
+ ... 'name1': ' a single line value ',
+ ... }
+ 1
+
+ >>> filename = a.filename
+ >>> a.filename = None
+ >>> values = a.write()
+ >>> index = 0
+ >>> while index < 23:
+ ... index += 1
+ ... line = values[index-1]
+ ... assert line.endswith('# comment ' + str(index))
+ >>> a.filename = filename
+
+ >>> start_comment = ['# Initial Comment', '', '#']
+ >>> end_comment = ['', '#', '# Final Comment']
+ >>> newconfig = start_comment + testconfig1.split('\\n') + end_comment
+ >>> nc = ConfigObj(newconfig)
+ >>> nc.initial_comment
+ ['# Initial Comment', '', '#']
+ >>> nc.final_comment
+ ['', '#', '# Final Comment']
+ >>> nc.initial_comment == start_comment
+ 1
+ >>> nc.final_comment == end_comment
+ 1
+
+ Test the _handle_comment method
+
+ >>> c = ConfigObj()
+ >>> c['foo'] = 'bar'
+ >>> c.inline_comments['foo'] = 'Nice bar'
+ >>> c.write()
+ ['foo = bar # Nice bar']
+
+ tekNico: FIXME: use StringIO instead of real files
+
+ >>> filename = a.filename
+ >>> a.filename = 'test.ini'
+ >>> a.write()
+ >>> a.filename = filename
+ >>> a == ConfigObj('test.ini', raise_errors=True)
+ 1
+ >>> os.remove('test.ini')
+ >>> b.filename = 'test.ini'
+ >>> b.write()
+ >>> b == ConfigObj('test.ini', raise_errors=True)
+ 1
+ >>> os.remove('test.ini')
+ >>> i.filename = 'test.ini'
+ >>> i.write()
+ >>> i == ConfigObj('test.ini', raise_errors=True)
+ 1
+ >>> os.remove('test.ini')
+ >>> a = ConfigObj()
+ >>> a['DEFAULT'] = {'a' : 'fish'}
+ >>> a['a'] = '%(a)s'
+ >>> a.write()
+ ['a = %(a)s', '[DEFAULT]', 'a = fish']
+
+ Test indentation handling
+
+ >>> ConfigObj({'sect': {'sect': {'foo': 'bar'}}}).write()
+ ['[sect]', ' [[sect]]', ' foo = bar']
+ >>> cfg = ['[sect]', '[[sect]]', 'foo = bar']
+ >>> ConfigObj(cfg).write() == cfg
+ 1
+ >>> cfg = ['[sect]', ' [[sect]]', ' foo = bar']
+ >>> ConfigObj(cfg).write() == cfg
+ 1
+ >>> cfg = ['[sect]', ' [[sect]]', ' foo = bar']
+ >>> ConfigObj(cfg).write() == cfg
+ 1
+ >>> ConfigObj(oneTabCfg).write() == oneTabCfg
+ 1
+ >>> ConfigObj(twoTabsCfg).write() == twoTabsCfg
+ 1
+ >>> ConfigObj(tabsAndSpacesCfg).write() == tabsAndSpacesCfg
+ 1
+ >>> ConfigObj(cfg, indent_type=chr(9)).write() == oneTabCfg
+ 1
+ >>> ConfigObj(oneTabCfg, indent_type=' ').write() == cfg
+ 1
+ """
+
+
+def _test_validate():
+ """
+ >>> config = '''
+ ... test1=40
+ ... test2=hello
+ ... test3=3
+ ... test4=5.0
+ ... [section]
+ ... test1=40
+ ... test2=hello
+ ... test3=3
+ ... test4=5.0
+ ... [[sub section]]
+ ... test1=40
+ ... test2=hello
+ ... test3=3
+ ... test4=5.0
+ ... '''.split('\\n')
+ >>> configspec = '''
+ ... test1= integer(30,50)
+ ... test2= string
+ ... test3=integer
+ ... test4=float(6.0)
+ ... [section ]
+ ... test1=integer(30,50)
+ ... test2=string
+ ... test3=integer
+ ... test4=float(6.0)
+ ... [[sub section]]
+ ... test1=integer(30,50)
+ ... test2=string
+ ... test3=integer
+ ... test4=float(6.0)
+ ... '''.split('\\n')
+ >>> val = Validator()
+ >>> c1 = ConfigObj(config, configspec=configspec)
+ >>> test = c1.validate(val)
+ >>> test == {
+ ... 'test1': True,
+ ... 'test2': True,
+ ... 'test3': True,
+ ... 'test4': False,
+ ... 'section': {
+ ... 'test1': True,
+ ... 'test2': True,
+ ... 'test3': True,
+ ... 'test4': False,
+ ... 'sub section': {
+ ... 'test1': True,
+ ... 'test2': True,
+ ... 'test3': True,
+ ... 'test4': False,
+ ... },
+ ... },
+ ... }
+ 1
+ >>> val.check(c1.configspec['test4'], c1['test4'])
+ Traceback (most recent call last):
+ VdtValueTooSmallError: the value "5.0" is too small.
+
+ >>> val_test_config = '''
+ ... key = 0
+ ... key2 = 1.1
+ ... [section]
+ ... key = some text
+ ... key2 = 1.1, 3.0, 17, 6.8
+ ... [[sub-section]]
+ ... key = option1
+ ... key2 = True'''.split('\\n')
+ >>> val_test_configspec = '''
+ ... key = integer
+ ... key2 = float
+ ... [section]
+ ... key = string
+ ... key2 = float_list(4)
+ ... [[sub-section]]
+ ... key = option(option1, option2)
+ ... key2 = boolean'''.split('\\n')
+ >>> val_test = ConfigObj(val_test_config, configspec=val_test_configspec)
+ >>> val_test.validate(val)
+ 1
+ >>> val_test['key'] = 'text not a digit'
+ >>> val_res = val_test.validate(val)
+ >>> val_res == {'key2': True, 'section': True, 'key': False}
+ 1
+ >>> configspec = '''
+ ... test1=integer(30,50, default=40)
+ ... test2=string(default="hello")
+ ... test3=integer(default=3)
+ ... test4=float(6.0, default=6.0)
+ ... [section ]
+ ... test1=integer(30,50, default=40)
+ ... test2=string(default="hello")
+ ... test3=integer(default=3)
+ ... test4=float(6.0, default=6.0)
+ ... [[sub section]]
+ ... test1=integer(30,50, default=40)
+ ... test2=string(default="hello")
+ ... test3=integer(default=3)
+ ... test4=float(6.0, default=6.0)
+ ... '''.split('\\n')
+ >>> default_test = ConfigObj(['test1=30'], configspec=configspec)
+ >>> default_test
+ ConfigObj({'test1': '30'})
+ >>> default_test.defaults
+ []
+ >>> default_test.default_values
+ {}
+ >>> default_test.validate(val)
+ 1
+ >>> default_test == {
+ ... 'test1': 30,
+ ... 'test2': 'hello',
+ ... 'test3': 3,
+ ... 'test4': 6.0,
+ ... 'section': {
+ ... 'test1': 40,
+ ... 'test2': 'hello',
+ ... 'test3': 3,
+ ... 'test4': 6.0,
+ ... 'sub section': {
+ ... 'test1': 40,
+ ... 'test3': 3,
+ ... 'test2': 'hello',
+ ... 'test4': 6.0,
+ ... },
+ ... },
+ ... }
+ 1
+ >>> default_test.defaults
+ ['test2', 'test3', 'test4']
+ >>> default_test.default_values == {'test1': 40, 'test2': 'hello',
+ ... 'test3': 3, 'test4': 6.0}
+ 1
+ >>> default_test.restore_default('test1')
+ 40
+ >>> default_test['test1']
+ 40
+ >>> 'test1' in default_test.defaults
+ 1
+ >>> def change(section, key):
+ ... section[key] = 3
+ >>> _ = default_test.walk(change)
+ >>> default_test['section']['sub section']['test4']
+ 3
+ >>> default_test.restore_defaults()
+ >>> default_test == {
+ ... 'test1': 40,
+ ... 'test2': "hello",
+ ... 'test3': 3,
+ ... 'test4': 6.0,
+ ... 'section': {
+ ... 'test1': 40,
+ ... 'test2': "hello",
+ ... 'test3': 3,
+ ... 'test4': 6.0,
+ ... 'sub section': {
+ ... 'test1': 40,
+ ... 'test2': "hello",
+ ... 'test3': 3,
+ ... 'test4': 6.0
+ ... }}}
+ 1
+
+ Now testing with repeated sections : BIG TEST
+
+ >>> repeated_1 = '''
+ ... [dogs]
+ ... [[__many__]] # spec for a dog
+ ... fleas = boolean(default=True)
+ ... tail = option(long, short, default=long)
+ ... name = string(default=rover)
+ ... [[[__many__]]] # spec for a puppy
+ ... name = string(default="son of rover")
+ ... age = float(default=0.0)
+ ... [cats]
+ ... [[__many__]] # spec for a cat
+ ... fleas = boolean(default=True)
+ ... tail = option(long, short, default=short)
+ ... name = string(default=pussy)
+ ... [[[__many__]]] # spec for a kitten
+ ... name = string(default="son of pussy")
+ ... age = float(default=0.0)
+ ... '''.split('\\n')
+ >>> repeated_2 = '''
+ ... [dogs]
+ ...
+ ... # blank dogs with puppies
+ ... # should be filled in by the configspec
+ ... [[dog1]]
+ ... [[[puppy1]]]
+ ... [[[puppy2]]]
+ ... [[[puppy3]]]
+ ... [[dog2]]
+ ... [[[puppy1]]]
+ ... [[[puppy2]]]
+ ... [[[puppy3]]]
+ ... [[dog3]]
+ ... [[[puppy1]]]
+ ... [[[puppy2]]]
+ ... [[[puppy3]]]
+ ... [cats]
+ ...
+ ... # blank cats with kittens
+ ... # should be filled in by the configspec
+ ... [[cat1]]
+ ... [[[kitten1]]]
+ ... [[[kitten2]]]
+ ... [[[kitten3]]]
+ ... [[cat2]]
+ ... [[[kitten1]]]
+ ... [[[kitten2]]]
+ ... [[[kitten3]]]
+ ... [[cat3]]
+ ... [[[kitten1]]]
+ ... [[[kitten2]]]
+ ... [[[kitten3]]]
+ ... '''.split('\\n')
+ >>> repeated_3 = '''
+ ... [dogs]
+ ...
+ ... [[dog1]]
+ ... [[dog2]]
+ ... [[dog3]]
+ ... [cats]
+ ...
+ ... [[cat1]]
+ ... [[cat2]]
+ ... [[cat3]]
+ ... '''.split('\\n')
+ >>> repeated_4 = '''
+ ... [__many__]
+ ...
+ ... name = string(default=Michael)
+ ... age = float(default=0.0)
+ ... sex = option(m, f, default=m)
+ ... '''.split('\\n')
+ >>> repeated_5 = '''
+ ... [cats]
+ ... [[__many__]]
+ ... fleas = boolean(default=True)
+ ... tail = option(long, short, default=short)
+ ... name = string(default=pussy)
+ ... [[[description]]]
+ ... height = float(default=3.3)
+ ... weight = float(default=6)
+ ... [[[[coat]]]]
+ ... fur = option(black, grey, brown, "tortoise shell", default=black)
+ ... condition = integer(0,10, default=5)
+ ... '''.split('\\n')
+ >>> val= Validator()
+ >>> repeater = ConfigObj(repeated_2, configspec=repeated_1)
+ >>> repeater.validate(val)
+ 1
+ >>> repeater == {
+ ... 'dogs': {
+ ... 'dog1': {
+ ... 'fleas': True,
+ ... 'tail': 'long',
+ ... 'name': 'rover',
+ ... 'puppy1': {'name': 'son of rover', 'age': 0.0},
+ ... 'puppy2': {'name': 'son of rover', 'age': 0.0},
+ ... 'puppy3': {'name': 'son of rover', 'age': 0.0},
+ ... },
+ ... 'dog2': {
+ ... 'fleas': True,
+ ... 'tail': 'long',
+ ... 'name': 'rover',
+ ... 'puppy1': {'name': 'son of rover', 'age': 0.0},
+ ... 'puppy2': {'name': 'son of rover', 'age': 0.0},
+ ... 'puppy3': {'name': 'son of rover', 'age': 0.0},
+ ... },
+ ... 'dog3': {
+ ... 'fleas': True,
+ ... 'tail': 'long',
+ ... 'name': 'rover',
+ ... 'puppy1': {'name': 'son of rover', 'age': 0.0},
+ ... 'puppy2': {'name': 'son of rover', 'age': 0.0},
+ ... 'puppy3': {'name': 'son of rover', 'age': 0.0},
+ ... },
+ ... },
+ ... 'cats': {
+ ... 'cat1': {
+ ... 'fleas': True,
+ ... 'tail': 'short',
+ ... 'name': 'pussy',
+ ... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
+ ... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
+ ... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
+ ... },
+ ... 'cat2': {
+ ... 'fleas': True,
+ ... 'tail': 'short',
+ ... 'name': 'pussy',
+ ... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
+ ... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
+ ... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
+ ... },
+ ... 'cat3': {
+ ... 'fleas': True,
+ ... 'tail': 'short',
+ ... 'name': 'pussy',
+ ... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
+ ... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
+ ... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
+ ... },
+ ... },
+ ... }
+ 1
+ >>> repeater = ConfigObj(repeated_3, configspec=repeated_1)
+ >>> repeater.validate(val)
+ 1
+ >>> repeater == {
+ ... 'cats': {
+ ... 'cat1': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
+ ... 'cat2': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
+ ... 'cat3': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
+ ... },
+ ... 'dogs': {
+ ... 'dog1': {'fleas': True, 'tail': 'long', 'name': 'rover'},
+ ... 'dog2': {'fleas': True, 'tail': 'long', 'name': 'rover'},
+ ... 'dog3': {'fleas': True, 'tail': 'long', 'name': 'rover'},
+ ... },
+ ... }
+ 1
+ >>> repeater = ConfigObj(configspec=repeated_4)
+ >>> repeater['Michael'] = {}
+ >>> repeater.validate(val)
+ 1
+ >>> repeater == {
+ ... 'Michael': {'age': 0.0, 'name': 'Michael', 'sex': 'm'},
+ ... }
+ 1
+ >>> repeater = ConfigObj(repeated_3, configspec=repeated_5)
+ >>> repeater == {
+ ... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
+ ... 'cats': {'cat1': {}, 'cat2': {}, 'cat3': {}},
+ ... }
+ 1
+ >>> repeater.validate(val)
+ 1
+ >>> repeater == {
+ ... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
+ ... 'cats': {
+ ... 'cat1': {
+ ... 'fleas': True,
+ ... 'tail': 'short',
+ ... 'name': 'pussy',
+ ... 'description': {
+ ... 'weight': 6.0,
+ ... 'height': 3.2999999999999998,
+ ... 'coat': {'fur': 'black', 'condition': 5},
+ ... },
+ ... },
+ ... 'cat2': {
+ ... 'fleas': True,
+ ... 'tail': 'short',
+ ... 'name': 'pussy',
+ ... 'description': {
+ ... 'weight': 6.0,
+ ... 'height': 3.2999999999999998,
+ ... 'coat': {'fur': 'black', 'condition': 5},
+ ... },
+ ... },
+ ... 'cat3': {
+ ... 'fleas': True,
+ ... 'tail': 'short',
+ ... 'name': 'pussy',
+ ... 'description': {
+ ... 'weight': 6.0,
+ ... 'height': 3.2999999999999998,
+ ... 'coat': {'fur': 'black', 'condition': 5},
+ ... },
+ ... },
+ ... },
+ ... }
+ 1
+
+ Test that interpolation is preserved for validated string values.
+ Also check that interpolation works in configspecs.
+ >>> t = ConfigObj(configspec=['test = string'])
+ >>> t['DEFAULT'] = {}
+ >>> t['DEFAULT']['def_test'] = 'a'
+ >>> t['test'] = '%(def_test)s'
+ >>> t['test']
+ 'a'
+ >>> v = Validator()
+ >>> t.validate(v)
+ 1
+ >>> t.interpolation = False
+ >>> t
+ ConfigObj({'test': '%(def_test)s', 'DEFAULT': {'def_test': 'a'}})
+ >>> specs = [
+ ... 'interpolated string = string(default="fuzzy-%(man)s")',
+ ... '[DEFAULT]',
+ ... 'man = wuzzy',
+ ... ]
+ >>> c = ConfigObj(configspec=specs)
+ >>> c.validate(v)
+ 1
+ >>> c['interpolated string']
+ 'fuzzy-wuzzy'
+
+ Test SimpleVal
+ >>> val = SimpleVal()
+ >>> config = '''
+ ... test1=40
+ ... test2=hello
+ ... test3=3
+ ... test4=5.0
+ ... [section]
+ ... test1=40
+ ... test2=hello
+ ... test3=3
+ ... test4=5.0
+ ... [[sub section]]
+ ... test1=40
+ ... test2=hello
+ ... test3=3
+ ... test4=5.0
+ ... '''.split('\\n')
+ >>> configspec = '''
+ ... test1=''
+ ... test2=''
+ ... test3=''
+ ... test4=''
+ ... [section]
+ ... test1=''
+ ... test2=''
+ ... test3=''
+ ... test4=''
+ ... [[sub section]]
+ ... test1=''
+ ... test2=''
+ ... test3=''
+ ... test4=''
+ ... '''.split('\\n')
+ >>> o = ConfigObj(config, configspec=configspec)
+ >>> o.validate(val)
+ 1
+ >>> o = ConfigObj(configspec=configspec)
+ >>> o.validate(val)
+ 0
+
+ Test Flatten Errors
+ >>> vtor = Validator()
+ >>> my_ini = '''
+ ... option1 = True
+ ... [section1]
+ ... option1 = True
+ ... [section2]
+ ... another_option = Probably
+ ... [section3]
+ ... another_option = True
+ ... [[section3b]]
+ ... value = 3
+ ... value2 = a
+ ... value3 = 11
+ ... '''
+ >>> my_cfg = '''
+ ... option1 = boolean()
+ ... option2 = boolean()
+ ... option3 = boolean(default=Bad_value)
+ ... [section1]
+ ... option1 = boolean()
+ ... option2 = boolean()
+ ... option3 = boolean(default=Bad_value)
+ ... [section2]
+ ... another_option = boolean()
+ ... [section3]
+ ... another_option = boolean()
+ ... [[section3b]]
+ ... value = integer
+ ... value2 = integer
+ ... value3 = integer(0, 10)
+ ... [[[section3b-sub]]]
+ ... value = string
+ ... [section4]
+ ... another_option = boolean()
+ ... '''
+ >>> cs = my_cfg.split('\\n')
+ >>> ini = my_ini.split('\\n')
+ >>> cfg = ConfigObj(ini, configspec=cs)
+ >>> res = cfg.validate(vtor, preserve_errors=True)
+ >>> errors = []
+ >>> for entry in flatten_errors(cfg, res):
+ ... section_list, key, error = entry
+ ... section_list.insert(0, '[root]')
+ ... if key is not None:
+ ... section_list.append(key)
+ ... else:
+ ... section_list.append('[missing]')
+ ... section_string = ', '.join(section_list)
+ ... errors.append((section_string, ' = ', error))
+ >>> errors.sort()
+ >>> for entry in errors:
+ ... print entry[0], entry[1], (entry[2] or 0)
+ [root], option2 = 0
+ [root], option3 = the value "Bad_value" is of the wrong type.
+ [root], section1, option2 = 0
+ [root], section1, option3 = the value "Bad_value" is of the wrong type.
+ [root], section2, another_option = the value "Probably" is of the wrong type.
+ [root], section3, section3b, section3b-sub, [missing] = 0
+ [root], section3, section3b, value2 = the value "a" is of the wrong type.
+ [root], section3, section3b, value3 = the value "11" is too big.
+ [root], section4, [missing] = 0
+ """
+
+
+def _test_errors():
+ """
+ Test the error messages and objects, in normal mode and unrepr mode.
+ >>> bad_syntax = '''
+ ... key = "value"
+ ... key2 = "value
+ ... '''.splitlines()
+ >>> c = ConfigObj(bad_syntax)
+ Traceback (most recent call last):
+ ParseError: Parse error in value at line 3.
+ >>> c = ConfigObj(bad_syntax, raise_errors=True)
+ Traceback (most recent call last):
+ ParseError: Parse error in value at line 3.
+ >>> c = ConfigObj(bad_syntax, raise_errors=True, unrepr=True)
+ Traceback (most recent call last):
+ UnreprError: Parse error in value at line 3.
+ >>> try:
+ ... c = ConfigObj(bad_syntax)
+ ... except Exception, e:
+ ... pass
+ >>> assert(isinstance(e, ConfigObjError))
+ >>> print e
+ Parse error in value at line 3.
+ >>> len(e.errors) == 1
+ 1
+ >>> try:
+ ... c = ConfigObj(bad_syntax, unrepr=True)
+ ... except Exception, e:
+ ... pass
+ >>> assert(isinstance(e, ConfigObjError))
+ >>> print e
+ Parse error in value at line 3.
+ >>> len(e.errors) == 1
+ 1
+ >>> the_error = e.errors[0]
+ >>> assert(isinstance(the_error, UnreprError))
+
+ >>> multiple_bad_syntax = '''
+ ... key = "value"
+ ... key2 = "value
+ ... key3 = "value2
+ ... '''.splitlines()
+ >>> try:
+ ... c = ConfigObj(multiple_bad_syntax)
+ ... except ConfigObjError, e:
+ ... str(e)
+ 'Parsing failed with several errors.\\nFirst error at line 3.'
+ >>> c = ConfigObj(multiple_bad_syntax, raise_errors=True)
+ Traceback (most recent call last):
+ ParseError: Parse error in value at line 3.
+ >>> c = ConfigObj(multiple_bad_syntax, raise_errors=True, unrepr=True)
+ Traceback (most recent call last):
+ UnreprError: Parse error in value at line 3.
+ >>> try:
+ ... c = ConfigObj(multiple_bad_syntax)
+ ... except Exception, e:
+ ... pass
+ >>> assert(isinstance(e, ConfigObjError))
+ >>> print e
+ Parsing failed with several errors.
+ First error at line 3.
+ >>> len(e.errors) == 2
+ 1
+ >>> try:
+ ... c = ConfigObj(multiple_bad_syntax, unrepr=True)
+ ... except Exception, e:
+ ... pass
+ >>> assert(isinstance(e, ConfigObjError))
+ >>> print e
+ Parsing failed with several errors.
+ First error at line 3.
+ >>> len(e.errors) == 2
+ 1
+ >>> the_error = e.errors[1]
+ >>> assert(isinstance(the_error, UnreprError))
+
+ >>> unknown_name = '''
+ ... key = "value"
+ ... key2 = value
+ ... '''.splitlines()
+ >>> c = ConfigObj(unknown_name)
+ >>> c = ConfigObj(unknown_name, unrepr=True)
+ Traceback (most recent call last):
+ UnreprError: Unknown name or type in value at line 3.
+ >>> c = ConfigObj(unknown_name, raise_errors=True, unrepr=True)
+ Traceback (most recent call last):
+ UnreprError: Unknown name or type in value at line 3.
+ """
+
+
+def _test_unrepr_comments():
+ """
+ >>> config = '''
+ ... # initial comments
+ ... # with two lines
+ ... key = "value"
+ ... # section comment
+ ... [section] # inline section comment
+ ... # key comment
+ ... key = "value"
+ ... # final comment
+ ... # with two lines
+ ... '''.splitlines()
+ >>> c = ConfigObj(config, unrepr=True)
+ >>> c == { 'key': 'value',
+ ... 'section': { 'key': 'value'}}
+ 1
+ >>> c.initial_comment == ['', '# initial comments', '# with two lines']
+ 1
+ >>> c.comments == {'section': ['# section comment'], 'key': []}
+ 1
+ >>> c.inline_comments == {'section': '# inline section comment', 'key': ''}
+ 1
+ >>> c['section'].comments == { 'key': ['# key comment']}
+ 1
+ >>> c.final_comment == ['# final comment', '# with two lines']
+ 1
+ """
+
+
+def _test_newline_terminated():
+ """
+ >>> c = ConfigObj()
+ >>> c.newlines = '\\n'
+ >>> c['a'] = 'b'
+ >>> collector = StringIO()
+ >>> c.write(collector)
+ >>> collector.getvalue()
+ 'a = b\\n'
+ """
+
+
+def _test_hash_escaping():
+ """
+ >>> c = ConfigObj()
+ >>> c.newlines = '\\n'
+ >>> c['#a'] = 'b # something'
+ >>> collector = StringIO()
+ >>> c.write(collector)
+ >>> collector.getvalue()
+ '"#a" = "b # something"\\n'
+
+ >>> c = ConfigObj()
+ >>> c.newlines = '\\n'
+ >>> c['a'] = 'b # something', 'c # something'
+ >>> collector = StringIO()
+ >>> c.write(collector)
+ >>> collector.getvalue()
+ 'a = "b # something", "c # something"\\n'
+ """
+
+
+def _test_lineendings():
+ """
+ NOTE: Need to use a real file because this code is only
+ exercised when reading from the filesystem.
+
+ >>> h = open('temp', 'wb')
+ >>> h.write('\\r\\n')
+ >>> h.close()
+ >>> c = ConfigObj('temp')
+ >>> c.newlines
+ '\\r\\n'
+ >>> h = open('temp', 'wb')
+ >>> h.write('\\n')
+ >>> h.close()
+ >>> c = ConfigObj('temp')
+ >>> c.newlines
+ '\\n'
+ >>> os.remove('temp')
+ """
+
+
+def _test_validate_with_copy_and_many():
+ """
+ >>> spec = '''
+ ... [section]
+ ... [[__many__]]
+ ... value = string(default='nothing')
+ ... '''
+ >>> config = '''
+ ... [section]
+ ... [[something]]
+ ... '''
+ >>> c = ConfigObj(StringIO(config), configspec=StringIO(spec))
+ >>> v = Validator()
+ >>> r = c.validate(v, copy=True)
+ >>> c['section']['something']['value']
+ 'nothing'
+ """
+
+def _test_configspec_with_hash():
+ """
+ >>> spec = ['stuff = string(default="#ff00dd")']
+ >>> c = ConfigObj(spec, _inspec=True)
+ >>> c['stuff']
+ 'string(default="#ff00dd")'
+ >>> c = ConfigObj(configspec=spec)
+ >>> v = Validator()
+ >>> c.validate(v)
+ 1
+ >>> c['stuff']
+ '#ff00dd'
+
+
+ >>> spec = ['stuff = string(default="fish") # wooble']
+ >>> c = ConfigObj(spec, _inspec=True)
+ >>> c['stuff']
+ 'string(default="fish") # wooble'
+ """
+
+def _test_many_check():
+ """
+ >>> spec = ['__many__ = integer()']
+ >>> config = ['a = 6', 'b = 7']
+ >>> c = ConfigObj(config, configspec=spec)
+ >>> v = Validator()
+ >>> c.validate(v)
+ 1
+ >>> type(c['a'])
+ <type 'int'>
+ >>> type(c['b'])
+ <type 'int'>
+
+
+ >>> spec = ['[name]', '__many__ = integer()']
+ >>> config = ['[name]', 'a = 6', 'b = 7']
+ >>> c = ConfigObj(config, configspec=spec)
+ >>> v = Validator()
+ >>> c.validate(v)
+ 1
+ >>> type(c['name']['a'])
+ <type 'int'>
+ >>> type(c['name']['b'])
+ <type 'int'>
+
+
+ >>> spec = ['[__many__]', '__many__ = integer()']
+ >>> config = ['[name]', 'hello = 7', '[thing]', 'fish = 0']
+ >>> c = ConfigObj(config, configspec=spec)
+ >>> v = Validator()
+ >>> c.validate(v)
+ 1
+ >>> type(c['name']['hello'])
+ <type 'int'>
+ >>> type(c['thing']['fish'])
+ <type 'int'>
+
+
+ >>> spec = '''
+ ... ___many___ = integer
+ ... [__many__]
+ ... ___many___ = boolean
+ ... [[__many__]]
+ ... __many__ = float
+ ... '''.splitlines()
+ >>> config = '''
+ ... fish = 8
+ ... buggle = 4
+ ... [hi]
+ ... one = true
+ ... two = false
+ ... [[bye]]
+ ... odd = 3
+ ... whoops = 9.0
+ ... [bye]
+ ... one = true
+ ... two = true
+ ... [[lots]]
+ ... odd = 3
+ ... whoops = 9.0
+ ... '''.splitlines()
+ >>> c = ConfigObj(config, configspec=spec)
+ >>> v = Validator()
+ >>> c.validate(v)
+ 1
+ >>> type(c['fish'])
+ <type 'int'>
+ >>> type(c['buggle'])
+ <type 'int'>
+ >>> c['hi']['one']
+ 1
+ >>> c['hi']['two']
+ 0
+ >>> type(c['hi']['bye']['odd'])
+ <type 'float'>
+ >>> type(c['hi']['bye']['whoops'])
+ <type 'float'>
+ >>> c['bye']['one']
+ 1
+ >>> c['bye']['two']
+ 1
+ >>> type(c['bye']['lots']['odd'])
+ <type 'float'>
+ >>> type(c['bye']['lots']['whoops'])
+ <type 'float'>
+
+
+ >>> spec = ['___many___ = integer()']
+ >>> config = ['a = 6', 'b = 7']
+ >>> c = ConfigObj(config, configspec=spec)
+ >>> v = Validator()
+ >>> c.validate(v)
+ 1
+ >>> type(c['a'])
+ <type 'int'>
+ >>> type(c['b'])
+ <type 'int'>
+
+
+ >>> spec = '''
+ ... [__many__]
+ ... [[__many__]]
+ ... __many__ = float
+ ... '''.splitlines()
+ >>> config = '''
+ ... [hi]
+ ... [[bye]]
+ ... odd = 3
+ ... whoops = 9.0
+ ... [bye]
+ ... [[lots]]
+ ... odd = 3
+ ... whoops = 9.0
+ ... '''.splitlines()
+ >>> c = ConfigObj(config, configspec=spec)
+ >>> v = Validator()
+ >>> c.validate(v)
+ 1
+ >>> type(c['hi']['bye']['odd'])
+ <type 'float'>
+ >>> type(c['hi']['bye']['whoops'])
+ <type 'float'>
+ >>> type(c['bye']['lots']['odd'])
+ <type 'float'>
+ >>> type(c['bye']['lots']['whoops'])
+ <type 'float'>
+
+ >>> s = ['[dog]', '[[cow]]', 'something = boolean', '[[__many__]]',
+ ... 'fish = integer']
+ >>> c = ['[dog]', '[[cow]]', 'something = true', '[[ob]]',
+ ... 'fish = 3', '[[bo]]', 'fish = 6']
+ >>> ini = ConfigObj(c, configspec=s)
+ >>> v = Validator()
+ >>> ini.validate(v)
+ 1
+ >>> ini['dog']['cow']['something']
+ 1
+ >>> ini['dog']['ob']['fish']
+ 3
+ >>> ini['dog']['bo']['fish']
+ 6
+
+
+ >>> s = ['[cow]', 'something = boolean', '[__many__]',
+ ... 'fish = integer']
+ >>> c = ['[cow]', 'something = true', '[ob]',
+ ... 'fish = 3', '[bo]', 'fish = 6']
+ >>> ini = ConfigObj(c, configspec=s)
+ >>> v = Validator()
+ >>> ini.validate(v)
+ 1
+ >>> ini['cow']['something']
+ 1
+ >>> ini['ob']['fish']
+ 3
+ >>> ini['bo']['fish']
+ 6
+ """
+
+
+def _unexpected_validation_errors():
+ """
+ Although the input is nonsensical we should not crash but correctly
+ report the failure to validate
+
+ # section specified, got scalar
+ >>> from validate import ValidateError
+ >>> s = ['[cow]', 'something = boolean']
+ >>> c = ['cow = true']
+ >>> ini = ConfigObj(c, configspec=s)
+ >>> v = Validator()
+ >>> ini.validate(v)
+ 0
+
+ >>> ini = ConfigObj(c, configspec=s)
+ >>> res = ini.validate(v, preserve_errors=True)
+ >>> check = flatten_errors(ini, res)
+ >>> for entry in check:
+ ... isinstance(entry[2], ValidateError)
+ ... print str(entry[2])
+ True
+ Section 'cow' was provided as a single value
+
+
+ # scalar specified, got section
+ >>> s = ['something = boolean']
+ >>> c = ['[something]', 'cow = true']
+ >>> ini = ConfigObj(c, configspec=s)
+ >>> v = Validator()
+ >>> ini.validate(v)
+ 0
+
+ >>> ini = ConfigObj(c, configspec=s)
+ >>> res = ini.validate(v, preserve_errors=True)
+ >>> check = flatten_errors(ini, res)
+ >>> for entry in check:
+ ... isinstance(entry[2], ValidateError)
+ ... print str(entry[2])
+ True
+ Value 'something' was provided as a section
+
+ # unexpected section
+ >>> s = []
+ >>> c = ['[cow]', 'dog = true']
+ >>> ini = ConfigObj(c, configspec=s)
+ >>> v = Validator()
+ >>> ini.validate(v)
+ 1
+
+
+ >>> s = ['[cow]', 'dog = bool']
+ >>> c = ['[cow]', 'dog = true']
+ >>> ini = ConfigObj(c, configspec=s)
+ >>> v = Validator()
+ >>> ini.validate(v, preserve_errors=True)
+ 1
+ """
+
+def _test_pickle():
+ """
+ >>> import pickle
+ >>> s = ['[cow]', 'dog = boolean']
+ >>> c = ['[cow]', 'dog = true']
+ >>> ini = ConfigObj(c, configspec=s)
+ >>> v = Validator()
+ >>> string = pickle.dumps(ini)
+ >>> new = pickle.loads(string)
+ >>> new.validate(v)
+ 1
+ """
+
+# drop support for Python 2.2 ?
+
+
+
+# TODO: Test BOM handling
+# TODO: Test error code for badly built multiline values
+# TODO: Test handling of StringIO
+# TODO: Test interpolation with writing
+
+
+if __name__ == '__main__':
+ # run the code tests in doctest format
+ #
+ testconfig1 = """\
+ key1= val # comment 1
+ key2= val # comment 2
+ # comment 3
+ [lev1a] # comment 4
+ key1= val # comment 5
+ key2= val # comment 6
+ # comment 7
+ [lev1b] # comment 8
+ key1= val # comment 9
+ key2= val # comment 10
+ # comment 11
+ [[lev2ba]] # comment 12
+ key1= val # comment 13
+ # comment 14
+ [[lev2bb]] # comment 15
+ key1= val # comment 16
+ # comment 17
+ [lev1c] # comment 18
+ # comment 19
+ [[lev2c]] # comment 20
+ # comment 21
+ [[[lev3c]]] # comment 22
+ key1 = val # comment 23"""
+ #
+ testconfig2 = """\
+ key1 = 'val1'
+ key2 = "val2"
+ key3 = val3
+ ["section 1"] # comment
+ keys11 = val1
+ keys12 = val2
+ keys13 = val3
+ [section 2]
+ keys21 = val1
+ keys22 = val2
+ keys23 = val3
+
+ [['section 2 sub 1']]
+ fish = 3
+ """
+ #
+ testconfig6 = '''
+ name1 = """ a single line value """ # comment
+ name2 = \''' another single line value \''' # comment
+ name3 = """ a single line value """
+ name4 = \''' another single line value \'''
+ [ "multi section" ]
+ name1 = """
+ Well, this is a
+ multiline value
+ """
+ name2 = \'''
+ Well, this is a
+ multiline value
+ \'''
+ name3 = """
+ Well, this is a
+ multiline value
+ """ # a comment
+ name4 = \'''
+ Well, this is a
+ multiline value
+ \''' # I guess this is a comment too
+ '''
+ #
+ # these cannot be put among the doctests, because the doctest module
+ # does a string.expandtabs() on all of them, sigh
+ oneTabCfg = ['[sect]', '\t[[sect]]', '\t\tfoo = bar']
+ twoTabsCfg = ['[sect]', '\t\t[[sect]]', '\t\t\t\tfoo = bar']
+ tabsAndSpacesCfg = ['[sect]', '\t \t [[sect]]', '\t \t \t \t foo = bar']
+ #
+ import doctest
+ m = sys.modules.get('__main__')
+ globs = m.__dict__.copy()
+ a = ConfigObj(testconfig1.split('\n'), raise_errors=True)
+ b = ConfigObj(testconfig2.split('\n'), raise_errors=True)
+ i = ConfigObj(testconfig6.split('\n'), raise_errors=True)
+ globs.update({'INTP_VER': INTP_VER, 'a': a, 'b': b, 'i': i,
+ 'oneTabCfg': oneTabCfg, 'twoTabsCfg': twoTabsCfg,
+ 'tabsAndSpacesCfg': tabsAndSpacesCfg})
+ doctest.testmod(m, globs=globs)
+
+
+# Man alive I prefer unittest ;-) \ No newline at end of file
diff --git a/validate.py b/validate.py
new file mode 100644
index 0000000..3370fe8
--- /dev/null
+++ b/validate.py
@@ -0,0 +1,1423 @@
+# validate.py
+# A Validator object
+# Copyright (C) 2005 Michael Foord, Mark Andrews, Nicola Larosa
+# E-mail: fuzzyman AT voidspace DOT org DOT uk
+# mark AT la-la DOT com
+# nico AT tekNico DOT net
+
+# This software is licensed under the terms of the BSD license.
+# http://www.voidspace.org.uk/python/license.shtml
+# Basically you're free to copy, modify, distribute and relicense it,
+# So long as you keep a copy of the license with it.
+
+# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
+# For information about bugfixes, updates and support, please join the
+# ConfigObj mailing list:
+# http://lists.sourceforge.net/lists/listinfo/configobj-develop
+# Comments, suggestions and bug reports welcome.
+
+"""
+ The Validator object is used to check that supplied values
+ conform to a specification.
+
+ The value can be supplied as a string - e.g. from a config file.
+ In this case the check will also *convert* the value to
+ the required type. This allows you to add validation
+ as a transparent layer to access data stored as strings.
+ The validation checks that the data is correct *and*
+ converts it to the expected type.
+
+ Some standard checks are provided for basic data types.
+ Additional checks are easy to write. They can be
+ provided when the ``Validator`` is instantiated or
+ added afterwards.
+
+ The standard functions work with the following basic data types :
+
+ * integers
+ * floats
+ * booleans
+ * strings
+ * ip_addr
+
+ plus lists of these datatypes
+
+ Adding additional checks is done through coding simple functions.
+
+ The full set of standard checks are :
+
+ * 'integer': matches integer values (including negative)
+ Takes optional 'min' and 'max' arguments : ::
+
+ integer()
+ integer(3, 9) # any value from 3 to 9
+ integer(min=0) # any positive value
+ integer(max=9)
+
+ * 'float': matches float values
+ Has the same parameters as the integer check.
+
+ * 'boolean': matches boolean values - ``True`` or ``False``
+ Acceptable string values for True are :
+ true, on, yes, 1
+ Acceptable string values for False are :
+ false, off, no, 0
+
+ Any other value raises an error.
+
+ * 'ip_addr': matches an Internet Protocol address, v.4, represented
+ by a dotted-quad string, i.e. '1.2.3.4'.
+
+ * 'string': matches any string.
+ Takes optional keyword args 'min' and 'max'
+ to specify min and max lengths of the string.
+
+ * 'list': matches any list.
+ Takes optional keyword args 'min', and 'max' to specify min and
+ max sizes of the list. (Always returns a list.)
+
+ * 'tuple': matches any tuple.
+ Takes optional keyword args 'min', and 'max' to specify min and
+ max sizes of the tuple. (Always returns a tuple.)
+
+ * 'int_list': Matches a list of integers.
+ Takes the same arguments as list.
+
+ * 'float_list': Matches a list of floats.
+ Takes the same arguments as list.
+
+ * 'bool_list': Matches a list of boolean values.
+ Takes the same arguments as list.
+
+ * 'ip_addr_list': Matches a list of IP addresses.
+ Takes the same arguments as list.
+
+ * 'string_list': Matches a list of strings.
+ Takes the same arguments as list.
+
+ * 'mixed_list': Matches a list with different types in
+ specific positions. List size must match
+ the number of arguments.
+
+ Each position can be one of :
+ 'integer', 'float', 'ip_addr', 'string', 'boolean'
+
+ So to specify a list with two strings followed
+ by two integers, you write the check as : ::
+
+ mixed_list('string', 'string', 'integer', 'integer')
+
+ * 'pass': This check matches everything ! It never fails
+ and the value is unchanged.
+
+ It is also the default if no check is specified.
+
+ * 'option': This check matches any from a list of options.
+ You specify this check with : ::
+
+ option('option 1', 'option 2', 'option 3')
+
+ You can supply a default value (returned if no value is supplied)
+ using the default keyword argument.
+
+ You specify a list argument for default using a list constructor syntax in
+ the check : ::
+
+ checkname(arg1, arg2, default=list('val 1', 'val 2', 'val 3'))
+
+ A badly formatted set of arguments will raise a ``VdtParamError``.
+"""
+
+__docformat__ = "restructuredtext en"
+
+__version__ = '0.3.2'
+
+__revision__ = '$Id: validate.py 123 2005-09-08 08:54:28Z fuzzyman $'
+
+__all__ = (
+ '__version__',
+ 'dottedQuadToNum',
+ 'numToDottedQuad',
+ 'ValidateError',
+ 'VdtUnknownCheckError',
+ 'VdtParamError',
+ 'VdtTypeError',
+ 'VdtValueError',
+ 'VdtValueTooSmallError',
+ 'VdtValueTooBigError',
+ 'VdtValueTooShortError',
+ 'VdtValueTooLongError',
+ 'VdtMissingValue',
+ 'Validator',
+ 'is_integer',
+ 'is_float',
+ 'is_boolean',
+ 'is_list',
+ 'is_tuple',
+ 'is_ip_addr',
+ 'is_string',
+ 'is_int_list',
+ 'is_bool_list',
+ 'is_float_list',
+ 'is_string_list',
+ 'is_ip_addr_list',
+ 'is_mixed_list',
+ 'is_option',
+ '__docformat__',
+)
+
+
+import sys
+INTP_VER = sys.version_info[:2]
+if INTP_VER < (2, 2):
+ raise RuntimeError("Python v.2.2 or later needed")
+
+import re
+StringTypes = (str, unicode)
+
+
+_list_arg = re.compile(r'''
+ (?:
+ ([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*list\(
+ (
+ (?:
+ \s*
+ (?:
+ (?:".*?")| # double quotes
+ (?:'.*?')| # single quotes
+ (?:[^'",\s\)][^,\)]*?) # unquoted
+ )
+ \s*,\s*
+ )*
+ (?:
+ (?:".*?")| # double quotes
+ (?:'.*?')| # single quotes
+ (?:[^'",\s\)][^,\)]*?) # unquoted
+ )? # last one
+ )
+ \)
+ )
+''', re.VERBOSE) # two groups
+
+_list_members = re.compile(r'''
+ (
+ (?:".*?")| # double quotes
+ (?:'.*?')| # single quotes
+ (?:[^'",\s=][^,=]*?) # unquoted
+ )
+ (?:
+ (?:\s*,\s*)|(?:\s*$) # comma
+ )
+''', re.VERBOSE) # one group
+
+_paramstring = r'''
+ (?:
+ (
+ (?:
+ [a-zA-Z_][a-zA-Z0-9_]*\s*=\s*list\(
+ (?:
+ \s*
+ (?:
+ (?:".*?")| # double quotes
+ (?:'.*?')| # single quotes
+ (?:[^'",\s\)][^,\)]*?) # unquoted
+ )
+ \s*,\s*
+ )*
+ (?:
+ (?:".*?")| # double quotes
+ (?:'.*?')| # single quotes
+ (?:[^'",\s\)][^,\)]*?) # unquoted
+ )? # last one
+ \)
+ )|
+ (?:
+ (?:".*?")| # double quotes
+ (?:'.*?')| # single quotes
+ (?:[^'",\s=][^,=]*?)| # unquoted
+ (?: # keyword argument
+ [a-zA-Z_][a-zA-Z0-9_]*\s*=\s*
+ (?:
+ (?:".*?")| # double quotes
+ (?:'.*?')| # single quotes
+ (?:[^'",\s=][^,=]*?) # unquoted
+ )
+ )
+ )
+ )
+ (?:
+ (?:\s*,\s*)|(?:\s*$) # comma
+ )
+ )
+ '''
+
+_matchstring = '^%s*' % _paramstring
+
+# Python pre 2.2.1 doesn't have bool
+try:
+ bool
+except NameError:
+ def bool(val):
+ """Simple boolean equivalent function. """
+ if val:
+ return 1
+ else:
+ return 0
+
+
+def dottedQuadToNum(ip):
+ """
+ Convert decimal dotted quad string to long integer
+
+ >>> int(dottedQuadToNum('1 '))
+ 1
+ >>> int(dottedQuadToNum(' 1.2'))
+ 16777218
+ >>> int(dottedQuadToNum(' 1.2.3 '))
+ 16908291
+ >>> int(dottedQuadToNum('1.2.3.4'))
+ 16909060
+ >>> dottedQuadToNum('1.2.3. 4')
+ 16909060
+ >>> dottedQuadToNum('255.255.255.255')
+ 4294967295L
+ >>> dottedQuadToNum('255.255.255.256')
+ Traceback (most recent call last):
+ ValueError: Not a good dotted-quad IP: 255.255.255.256
+ """
+
+ # import here to avoid it when ip_addr values are not used
+ import socket, struct
+
+ try:
+ return struct.unpack('!L',
+ socket.inet_aton(ip.strip()))[0]
+ except socket.error:
+ # bug in inet_aton, corrected in Python 2.3
+ if ip.strip() == '255.255.255.255':
+ return 0xFFFFFFFFL
+ else:
+ raise ValueError('Not a good dotted-quad IP: %s' % ip)
+ return
+
+
+def numToDottedQuad(num):
+ """
+ Convert long int to dotted quad string
+
+ >>> numToDottedQuad(-1L)
+ Traceback (most recent call last):
+ ValueError: Not a good numeric IP: -1
+ >>> numToDottedQuad(1L)
+ '0.0.0.1'
+ >>> numToDottedQuad(16777218L)
+ '1.0.0.2'
+ >>> numToDottedQuad(16908291L)
+ '1.2.0.3'
+ >>> numToDottedQuad(16909060L)
+ '1.2.3.4'
+ >>> numToDottedQuad(4294967295L)
+ '255.255.255.255'
+ >>> numToDottedQuad(4294967296L)
+ Traceback (most recent call last):
+ ValueError: Not a good numeric IP: 4294967296
+ """
+
+ # import here to avoid it when ip_addr values are not used
+ import socket, struct
+
+ # no need to intercept here, 4294967295L is fine
+ if num > 4294967295L or num < 0:
+ raise ValueError('Not a good numeric IP: %s' % num)
+ try:
+ return socket.inet_ntoa(
+ struct.pack('!L', long(num)))
+ except (socket.error, struct.error, OverflowError):
+ raise ValueError('Not a good numeric IP: %s' % num)
+
+
+class ValidateError(Exception):
+ """
+ This error indicates that the check failed.
+ It can be the base class for more specific errors.
+
+ Any check function that fails ought to raise this error.
+ (or a subclass)
+
+ >>> raise ValidateError
+ Traceback (most recent call last):
+ ValidateError
+ """
+
+
+class VdtMissingValue(ValidateError):
+ """No value was supplied to a check that needed one."""
+
+
+class VdtUnknownCheckError(ValidateError):
+ """An unknown check function was requested"""
+
+ def __init__(self, value):
+ """
+ >>> raise VdtUnknownCheckError('yoda')
+ Traceback (most recent call last):
+ VdtUnknownCheckError: the check "yoda" is unknown.
+ """
+ ValidateError.__init__(self, 'the check "%s" is unknown.' % (value,))
+
+
+class VdtParamError(SyntaxError):
+ """An incorrect parameter was passed"""
+
+ def __init__(self, name, value):
+ """
+ >>> raise VdtParamError('yoda', 'jedi')
+ Traceback (most recent call last):
+ VdtParamError: passed an incorrect value "jedi" for parameter "yoda".
+ """
+ SyntaxError.__init__(self, 'passed an incorrect value "%s" for parameter "%s".' % (value, name))
+
+
+class VdtTypeError(ValidateError):
+ """The value supplied was of the wrong type"""
+
+ def __init__(self, value):
+ """
+ >>> raise VdtTypeError('jedi')
+ Traceback (most recent call last):
+ VdtTypeError: the value "jedi" is of the wrong type.
+ """
+ ValidateError.__init__(self, 'the value "%s" is of the wrong type.' % (value,))
+
+
+class VdtValueError(ValidateError):
+ """The value supplied was of the correct type, but was not an allowed value."""
+
+ def __init__(self, value):
+ """
+ >>> raise VdtValueError('jedi')
+ Traceback (most recent call last):
+ VdtValueError: the value "jedi" is unacceptable.
+ """
+ ValidateError.__init__(self, 'the value "%s" is unacceptable.' % (value,))
+
+
+class VdtValueTooSmallError(VdtValueError):
+ """The value supplied was of the correct type, but was too small."""
+
+ def __init__(self, value):
+ """
+ >>> raise VdtValueTooSmallError('0')
+ Traceback (most recent call last):
+ VdtValueTooSmallError: the value "0" is too small.
+ """
+ ValidateError.__init__(self, 'the value "%s" is too small.' % (value,))
+
+
+class VdtValueTooBigError(VdtValueError):
+ """The value supplied was of the correct type, but was too big."""
+
+ def __init__(self, value):
+ """
+ >>> raise VdtValueTooBigError('1')
+ Traceback (most recent call last):
+ VdtValueTooBigError: the value "1" is too big.
+ """
+ ValidateError.__init__(self, 'the value "%s" is too big.' % (value,))
+
+
+class VdtValueTooShortError(VdtValueError):
+ """The value supplied was of the correct type, but was too short."""
+
+ def __init__(self, value):
+ """
+ >>> raise VdtValueTooShortError('jed')
+ Traceback (most recent call last):
+ VdtValueTooShortError: the value "jed" is too short.
+ """
+ ValidateError.__init__(
+ self,
+ 'the value "%s" is too short.' % (value,))
+
+
+class VdtValueTooLongError(VdtValueError):
+ """The value supplied was of the correct type, but was too long."""
+
+ def __init__(self, value):
+ """
+ >>> raise VdtValueTooLongError('jedie')
+ Traceback (most recent call last):
+ VdtValueTooLongError: the value "jedie" is too long.
+ """
+ ValidateError.__init__(self, 'the value "%s" is too long.' % (value,))
+
+
+class Validator(object):
+ """
+ Validator is an object that allows you to register a set of 'checks'.
+ These checks take input and test that it conforms to the check.
+
+ This can also involve converting the value from a string into
+ the correct datatype.
+
+ The ``check`` method takes an input string which configures which
+ check is to be used and applies that check to a supplied value.
+
+ An example input string would be:
+ 'int_range(param1, param2)'
+
+ You would then provide something like:
+
+ >>> def int_range_check(value, min, max):
+ ... # turn min and max from strings to integers
+ ... min = int(min)
+ ... max = int(max)
+ ... # check that value is of the correct type.
+ ... # possible valid inputs are integers or strings
+ ... # that represent integers
+ ... if not isinstance(value, (int, long, StringTypes)):
+ ... raise VdtTypeError(value)
+ ... elif isinstance(value, StringTypes):
+ ... # if we are given a string
+ ... # attempt to convert to an integer
+ ... try:
+ ... value = int(value)
+ ... except ValueError:
+ ... raise VdtValueError(value)
+ ... # check the value is between our constraints
+ ... if not min <= value:
+ ... raise VdtValueTooSmallError(value)
+ ... if not value <= max:
+ ... raise VdtValueTooBigError(value)
+ ... return value
+
+ >>> fdict = {'int_range': int_range_check}
+ >>> vtr1 = Validator(fdict)
+ >>> vtr1.check('int_range(20, 40)', '30')
+ 30
+ >>> vtr1.check('int_range(20, 40)', '60')
+ Traceback (most recent call last):
+ VdtValueTooBigError: the value "60" is too big.
+
+ New functions can be added with : ::
+
+ >>> vtr2 = Validator()
+ >>> vtr2.functions['int_range'] = int_range_check
+
+ Or by passing in a dictionary of functions when Validator
+ is instantiated.
+
+ Your functions *can* use keyword arguments,
+ but the first argument should always be 'value'.
+
+ If the function doesn't take additional arguments,
+ the parentheses are optional in the check.
+ It can be written with either of : ::
+
+ keyword = function_name
+ keyword = function_name()
+
+ The first program to utilise Validator() was Michael Foord's
+ ConfigObj, an alternative to ConfigParser which supports lists and
+ can validate a config file using a config schema.
+ For more details on using Validator with ConfigObj see:
+ http://www.voidspace.org.uk/python/configobj.html
+ """
+
+ # this regex does the initial parsing of the checks
+ _func_re = re.compile(r'(.+?)\((.*)\)')
+
+ # this regex takes apart keyword arguments
+ _key_arg = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$')
+
+
+ # this regex finds keyword=list(....) type values
+ _list_arg = _list_arg
+
+ # this regex takes individual values out of lists - in one pass
+ _list_members = _list_members
+
+ # These regexes check a set of arguments for validity
+ # and then pull the members out
+ _paramfinder = re.compile(_paramstring, re.VERBOSE)
+ _matchfinder = re.compile(_matchstring, re.VERBOSE)
+
+
+ def __init__(self, functions=None):
+ """
+ >>> vtri = Validator()
+ """
+ self.functions = {
+ '': self._pass,
+ 'integer': is_integer,
+ 'float': is_float,
+ 'boolean': is_boolean,
+ 'ip_addr': is_ip_addr,
+ 'string': is_string,
+ 'list': is_list,
+ 'tuple': is_tuple,
+ 'int_list': is_int_list,
+ 'float_list': is_float_list,
+ 'bool_list': is_bool_list,
+ 'ip_addr_list': is_ip_addr_list,
+ 'string_list': is_string_list,
+ 'mixed_list': is_mixed_list,
+ 'pass': self._pass,
+ 'option': is_option,
+ }
+ if functions is not None:
+ self.functions.update(functions)
+ # tekNico: for use by ConfigObj
+ self.baseErrorClass = ValidateError
+ self._cache = {}
+
+
+ def check(self, check, value, missing=False):
+ """
+ Usage: check(check, value)
+
+ Arguments:
+ check: string representing check to apply (including arguments)
+ value: object to be checked
+ Returns value, converted to correct type if necessary
+
+ If the check fails, raises a ``ValidateError`` subclass.
+
+ >>> vtor.check('yoda', '')
+ Traceback (most recent call last):
+ VdtUnknownCheckError: the check "yoda" is unknown.
+ >>> vtor.check('yoda()', '')
+ Traceback (most recent call last):
+ VdtUnknownCheckError: the check "yoda" is unknown.
+
+ >>> vtor.check('string(default="")', '', missing=True)
+ ''
+ """
+ fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check)
+
+ if missing:
+ if default is None:
+ # no information needed here - to be handled by caller
+ raise VdtMissingValue()
+ value = self._handle_none(default)
+
+ if value is None:
+ return None
+
+ return self._check_value(value, fun_name, fun_args, fun_kwargs)
+
+
+ def _handle_none(self, value):
+ if value == 'None':
+ value = None
+ elif value in ("'None'", '"None"'):
+ # Special case a quoted None
+ value = self._unquote(value)
+ return value
+
+
+ def _parse_with_caching(self, check):
+ if check in self._cache:
+ fun_name, fun_args, fun_kwargs, default = self._cache[check]
+ # We call list and dict below to work with *copies* of the data
+ # rather than the original (which are mutable of course)
+ fun_args = list(fun_args)
+ fun_kwargs = dict(fun_kwargs)
+ else:
+ fun_name, fun_args, fun_kwargs, default = self._parse_check(check)
+ fun_kwargs = dict((str(key), value) for (key, value) in fun_kwargs.items())
+ self._cache[check] = fun_name, list(fun_args), dict(fun_kwargs), default
+ return fun_name, fun_args, fun_kwargs, default
+
+
+ def _check_value(self, value, fun_name, fun_args, fun_kwargs):
+ try:
+ fun = self.functions[fun_name]
+ except KeyError:
+ raise VdtUnknownCheckError(fun_name)
+ else:
+ return fun(value, *fun_args, **fun_kwargs)
+
+
+ def _parse_check(self, check):
+ fun_match = self._func_re.match(check)
+ if fun_match:
+ fun_name = fun_match.group(1)
+ arg_string = fun_match.group(2)
+ arg_match = self._matchfinder.match(arg_string)
+ if arg_match is None:
+ # Bad syntax
+ raise VdtParamError('Bad syntax in check "%s".' % check)
+ fun_args = []
+ fun_kwargs = {}
+ # pull out args of group 2
+ for arg in self._paramfinder.findall(arg_string):
+ # args may need whitespace removing (before removing quotes)
+ arg = arg.strip()
+ listmatch = self._list_arg.match(arg)
+ if listmatch:
+ key, val = self._list_handle(listmatch)
+ fun_kwargs[key] = val
+ continue
+ keymatch = self._key_arg.match(arg)
+ if keymatch:
+ val = keymatch.group(2)
+ if not val in ("'None'", '"None"'):
+ # Special case a quoted None
+ val = self._unquote(val)
+ fun_kwargs[keymatch.group(1)] = val
+ continue
+
+ fun_args.append(self._unquote(arg))
+ else:
+ # allows for function names without (args)
+ return check, (), {}, None
+
+ # Default must be deleted if the value is specified too,
+ # otherwise the check function will get a spurious "default" keyword arg
+ try:
+ default = fun_kwargs.pop('default', None)
+ except AttributeError:
+ # Python 2.2 compatibility
+ default = None
+ try:
+ default = fun_kwargs['default']
+ del fun_kwargs['default']
+ except KeyError:
+ pass
+
+ return fun_name, fun_args, fun_kwargs, default
+
+
+ def _unquote(self, val):
+ """Unquote a value if necessary."""
+ if (len(val) >= 2) and (val[0] in ("'", '"')) and (val[0] == val[-1]):
+ val = val[1:-1]
+ return val
+
+
+ def _list_handle(self, listmatch):
+ """Take apart a ``keyword=list('val, 'val')`` type string."""
+ out = []
+ name = listmatch.group(1)
+ args = listmatch.group(2)
+ for arg in self._list_members.findall(args):
+ out.append(self._unquote(arg))
+ return name, out
+
+
+ def _pass(self, value):
+ """
+ Dummy check that always passes
+
+ >>> vtor.check('', 0)
+ 0
+ >>> vtor.check('', '0')
+ '0'
+ """
+ return value
+
+
+ def get_default_value(self, check):
+ """
+ Given a check, return the default value for the check
+ (converted to the right type).
+
+ If the check doesn't specify a default value then a
+ ``KeyError`` will be raised.
+ """
+ fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check)
+ if default is None:
+ raise KeyError('Check "%s" has no default value.' % check)
+ value = self._handle_none(default)
+ if value is None:
+ return value
+ return self._check_value(value, fun_name, fun_args, fun_kwargs)
+
+
+def _is_num_param(names, values, to_float=False):
+ """
+ Return numbers from inputs or raise VdtParamError.
+
+ Lets ``None`` pass through.
+ Pass in keyword argument ``to_float=True`` to
+ use float for the conversion rather than int.
+
+ >>> _is_num_param(('', ''), (0, 1.0))
+ [0, 1]
+ >>> _is_num_param(('', ''), (0, 1.0), to_float=True)
+ [0.0, 1.0]
+ >>> _is_num_param(('a'), ('a'))
+ Traceback (most recent call last):
+ VdtParamError: passed an incorrect value "a" for parameter "a".
+ """
+ fun = to_float and float or int
+ out_params = []
+ for (name, val) in zip(names, values):
+ if val is None:
+ out_params.append(val)
+ elif isinstance(val, (int, long, float, StringTypes)):
+ try:
+ out_params.append(fun(val))
+ except ValueError, e:
+ raise VdtParamError(name, val)
+ else:
+ raise VdtParamError(name, val)
+ return out_params
+
+
+# built in checks
+# you can override these by setting the appropriate name
+# in Validator.functions
+# note: if the params are specified wrongly in your input string,
+# you will also raise errors.
+
+def is_integer(value, min=None, max=None):
+ """
+ A check that tests that a given value is an integer (int, or long)
+ and optionally, between bounds. A negative value is accepted, while
+ a float will fail.
+
+ If the value is a string, then the conversion is done - if possible.
+ Otherwise a VdtError is raised.
+
+ >>> vtor.check('integer', '-1')
+ -1
+ >>> vtor.check('integer', '0')
+ 0
+ >>> vtor.check('integer', 9)
+ 9
+ >>> vtor.check('integer', 'a')
+ Traceback (most recent call last):
+ VdtTypeError: the value "a" is of the wrong type.
+ >>> vtor.check('integer', '2.2')
+ Traceback (most recent call last):
+ VdtTypeError: the value "2.2" is of the wrong type.
+ >>> vtor.check('integer(10)', '20')
+ 20
+ >>> vtor.check('integer(max=20)', '15')
+ 15
+ >>> vtor.check('integer(10)', '9')
+ Traceback (most recent call last):
+ VdtValueTooSmallError: the value "9" is too small.
+ >>> vtor.check('integer(10)', 9)
+ Traceback (most recent call last):
+ VdtValueTooSmallError: the value "9" is too small.
+ >>> vtor.check('integer(max=20)', '35')
+ Traceback (most recent call last):
+ VdtValueTooBigError: the value "35" is too big.
+ >>> vtor.check('integer(max=20)', 35)
+ Traceback (most recent call last):
+ VdtValueTooBigError: the value "35" is too big.
+ >>> vtor.check('integer(0, 9)', False)
+ 0
+ """
+ (min_val, max_val) = _is_num_param(('min', 'max'), (min, max))
+ if not isinstance(value, (int, long, StringTypes)):
+ raise VdtTypeError(value)
+ if isinstance(value, StringTypes):
+ # if it's a string - does it represent an integer ?
+ try:
+ value = int(value)
+ except ValueError:
+ raise VdtTypeError(value)
+ if (min_val is not None) and (value < min_val):
+ raise VdtValueTooSmallError(value)
+ if (max_val is not None) and (value > max_val):
+ raise VdtValueTooBigError(value)
+ return value
+
+
+def is_float(value, min=None, max=None):
+ """
+ A check that tests that a given value is a float
+ (an integer will be accepted), and optionally - that it is between bounds.
+
+ If the value is a string, then the conversion is done - if possible.
+ Otherwise a VdtError is raised.
+
+ This can accept negative values.
+
+ >>> vtor.check('float', '2')
+ 2.0
+
+ From now on we multiply the value to avoid comparing decimals
+
+ >>> vtor.check('float', '-6.8') * 10
+ -68.0
+ >>> vtor.check('float', '12.2') * 10
+ 122.0
+ >>> vtor.check('float', 8.4) * 10
+ 84.0
+ >>> vtor.check('float', 'a')
+ Traceback (most recent call last):
+ VdtTypeError: the value "a" is of the wrong type.
+ >>> vtor.check('float(10.1)', '10.2') * 10
+ 102.0
+ >>> vtor.check('float(max=20.2)', '15.1') * 10
+ 151.0
+ >>> vtor.check('float(10.0)', '9.0')
+ Traceback (most recent call last):
+ VdtValueTooSmallError: the value "9.0" is too small.
+ >>> vtor.check('float(max=20.0)', '35.0')
+ Traceback (most recent call last):
+ VdtValueTooBigError: the value "35.0" is too big.
+ """
+ (min_val, max_val) = _is_num_param(
+ ('min', 'max'), (min, max), to_float=True)
+ if not isinstance(value, (int, long, float, StringTypes)):
+ raise VdtTypeError(value)
+ if not isinstance(value, float):
+ # if it's a string - does it represent a float ?
+ try:
+ value = float(value)
+ except ValueError:
+ raise VdtTypeError(value)
+ if (min_val is not None) and (value < min_val):
+ raise VdtValueTooSmallError(value)
+ if (max_val is not None) and (value > max_val):
+ raise VdtValueTooBigError(value)
+ return value
+
+
+bool_dict = {
+ True: True, 'on': True, '1': True, 'true': True, 'yes': True,
+ False: False, 'off': False, '0': False, 'false': False, 'no': False,
+}
+
+
+def is_boolean(value):
+ """
+ Check if the value represents a boolean.
+
+ >>> vtor.check('boolean', 0)
+ 0
+ >>> vtor.check('boolean', False)
+ 0
+ >>> vtor.check('boolean', '0')
+ 0
+ >>> vtor.check('boolean', 'off')
+ 0
+ >>> vtor.check('boolean', 'false')
+ 0
+ >>> vtor.check('boolean', 'no')
+ 0
+ >>> vtor.check('boolean', 'nO')
+ 0
+ >>> vtor.check('boolean', 'NO')
+ 0
+ >>> vtor.check('boolean', 1)
+ 1
+ >>> vtor.check('boolean', True)
+ 1
+ >>> vtor.check('boolean', '1')
+ 1
+ >>> vtor.check('boolean', 'on')
+ 1
+ >>> vtor.check('boolean', 'true')
+ 1
+ >>> vtor.check('boolean', 'yes')
+ 1
+ >>> vtor.check('boolean', 'Yes')
+ 1
+ >>> vtor.check('boolean', 'YES')
+ 1
+ >>> vtor.check('boolean', '')
+ Traceback (most recent call last):
+ VdtTypeError: the value "" is of the wrong type.
+ >>> vtor.check('boolean', 'up')
+ Traceback (most recent call last):
+ VdtTypeError: the value "up" is of the wrong type.
+
+ """
+ if isinstance(value, StringTypes):
+ try:
+ return bool_dict[value.lower()]
+ except KeyError:
+ raise VdtTypeError(value)
+ # we do an equality test rather than an identity test
+ # this ensures Python 2.2 compatibilty
+ # and allows 0 and 1 to represent True and False
+ if value == False:
+ return False
+ elif value == True:
+ return True
+ else:
+ raise VdtTypeError(value)
+
+
+def is_ip_addr(value):
+ """
+ Check that the supplied value is an Internet Protocol address, v.4,
+ represented by a dotted-quad string, i.e. '1.2.3.4'.
+
+ >>> vtor.check('ip_addr', '1 ')
+ '1'
+ >>> vtor.check('ip_addr', ' 1.2')
+ '1.2'
+ >>> vtor.check('ip_addr', ' 1.2.3 ')
+ '1.2.3'
+ >>> vtor.check('ip_addr', '1.2.3.4')
+ '1.2.3.4'
+ >>> vtor.check('ip_addr', '0.0.0.0')
+ '0.0.0.0'
+ >>> vtor.check('ip_addr', '255.255.255.255')
+ '255.255.255.255'
+ >>> vtor.check('ip_addr', '255.255.255.256')
+ Traceback (most recent call last):
+ VdtValueError: the value "255.255.255.256" is unacceptable.
+ >>> vtor.check('ip_addr', '1.2.3.4.5')
+ Traceback (most recent call last):
+ VdtValueError: the value "1.2.3.4.5" is unacceptable.
+ >>> vtor.check('ip_addr', 0)
+ Traceback (most recent call last):
+ VdtTypeError: the value "0" is of the wrong type.
+ """
+ if not isinstance(value, StringTypes):
+ raise VdtTypeError(value)
+ value = value.strip()
+ try:
+ dottedQuadToNum(value)
+ except ValueError:
+ raise VdtValueError(value)
+ return value
+
+
+def is_list(value, min=None, max=None):
+ """
+ Check that the value is a list of values.
+
+ You can optionally specify the minimum and maximum number of members.
+
+ It does no check on list members.
+
+ >>> vtor.check('list', ())
+ []
+ >>> vtor.check('list', [])
+ []
+ >>> vtor.check('list', (1, 2))
+ [1, 2]
+ >>> vtor.check('list', [1, 2])
+ [1, 2]
+ >>> vtor.check('list(3)', (1, 2))
+ Traceback (most recent call last):
+ VdtValueTooShortError: the value "(1, 2)" is too short.
+ >>> vtor.check('list(max=5)', (1, 2, 3, 4, 5, 6))
+ Traceback (most recent call last):
+ VdtValueTooLongError: the value "(1, 2, 3, 4, 5, 6)" is too long.
+ >>> vtor.check('list(min=3, max=5)', (1, 2, 3, 4))
+ [1, 2, 3, 4]
+ >>> vtor.check('list', 0)
+ Traceback (most recent call last):
+ VdtTypeError: the value "0" is of the wrong type.
+ >>> vtor.check('list', '12')
+ Traceback (most recent call last):
+ VdtTypeError: the value "12" is of the wrong type.
+ """
+ (min_len, max_len) = _is_num_param(('min', 'max'), (min, max))
+ if isinstance(value, StringTypes):
+ raise VdtTypeError(value)
+ try:
+ num_members = len(value)
+ except TypeError:
+ raise VdtTypeError(value)
+ if min_len is not None and num_members < min_len:
+ raise VdtValueTooShortError(value)
+ if max_len is not None and num_members > max_len:
+ raise VdtValueTooLongError(value)
+ return list(value)
+
+
+def is_tuple(value, min=None, max=None):
+ """
+ Check that the value is a tuple of values.
+
+ You can optionally specify the minimum and maximum number of members.
+
+ It does no check on members.
+
+ >>> vtor.check('tuple', ())
+ ()
+ >>> vtor.check('tuple', [])
+ ()
+ >>> vtor.check('tuple', (1, 2))
+ (1, 2)
+ >>> vtor.check('tuple', [1, 2])
+ (1, 2)
+ >>> vtor.check('tuple(3)', (1, 2))
+ Traceback (most recent call last):
+ VdtValueTooShortError: the value "(1, 2)" is too short.
+ >>> vtor.check('tuple(max=5)', (1, 2, 3, 4, 5, 6))
+ Traceback (most recent call last):
+ VdtValueTooLongError: the value "(1, 2, 3, 4, 5, 6)" is too long.
+ >>> vtor.check('tuple(min=3, max=5)', (1, 2, 3, 4))
+ (1, 2, 3, 4)
+ >>> vtor.check('tuple', 0)
+ Traceback (most recent call last):
+ VdtTypeError: the value "0" is of the wrong type.
+ >>> vtor.check('tuple', '12')
+ Traceback (most recent call last):
+ VdtTypeError: the value "12" is of the wrong type.
+ """
+ return tuple(is_list(value, min, max))
+
+
+def is_string(value, min=None, max=None):
+ """
+ Check that the supplied value is a string.
+
+ You can optionally specify the minimum and maximum number of members.
+
+ >>> vtor.check('string', '0')
+ '0'
+ >>> vtor.check('string', 0)
+ Traceback (most recent call last):
+ VdtTypeError: the value "0" is of the wrong type.
+ >>> vtor.check('string(2)', '12')
+ '12'
+ >>> vtor.check('string(2)', '1')
+ Traceback (most recent call last):
+ VdtValueTooShortError: the value "1" is too short.
+ >>> vtor.check('string(min=2, max=3)', '123')
+ '123'
+ >>> vtor.check('string(min=2, max=3)', '1234')
+ Traceback (most recent call last):
+ VdtValueTooLongError: the value "1234" is too long.
+ """
+ if not isinstance(value, StringTypes):
+ raise VdtTypeError(value)
+ (min_len, max_len) = _is_num_param(('min', 'max'), (min, max))
+ try:
+ num_members = len(value)
+ except TypeError:
+ raise VdtTypeError(value)
+ if min_len is not None and num_members < min_len:
+ raise VdtValueTooShortError(value)
+ if max_len is not None and num_members > max_len:
+ raise VdtValueTooLongError(value)
+ return value
+
+
+def is_int_list(value, min=None, max=None):
+ """
+ Check that the value is a list of integers.
+
+ You can optionally specify the minimum and maximum number of members.
+
+ Each list member is checked that it is an integer.
+
+ >>> vtor.check('int_list', ())
+ []
+ >>> vtor.check('int_list', [])
+ []
+ >>> vtor.check('int_list', (1, 2))
+ [1, 2]
+ >>> vtor.check('int_list', [1, 2])
+ [1, 2]
+ >>> vtor.check('int_list', [1, 'a'])
+ Traceback (most recent call last):
+ VdtTypeError: the value "a" is of the wrong type.
+ """
+ return [is_integer(mem) for mem in is_list(value, min, max)]
+
+
+def is_bool_list(value, min=None, max=None):
+ """
+ Check that the value is a list of booleans.
+
+ You can optionally specify the minimum and maximum number of members.
+
+ Each list member is checked that it is a boolean.
+
+ >>> vtor.check('bool_list', ())
+ []
+ >>> vtor.check('bool_list', [])
+ []
+ >>> check_res = vtor.check('bool_list', (True, False))
+ >>> check_res == [True, False]
+ 1
+ >>> check_res = vtor.check('bool_list', [True, False])
+ >>> check_res == [True, False]
+ 1
+ >>> vtor.check('bool_list', [True, 'a'])
+ Traceback (most recent call last):
+ VdtTypeError: the value "a" is of the wrong type.
+ """
+ return [is_boolean(mem) for mem in is_list(value, min, max)]
+
+
+def is_float_list(value, min=None, max=None):
+ """
+ Check that the value is a list of floats.
+
+ You can optionally specify the minimum and maximum number of members.
+
+ Each list member is checked that it is a float.
+
+ >>> vtor.check('float_list', ())
+ []
+ >>> vtor.check('float_list', [])
+ []
+ >>> vtor.check('float_list', (1, 2.0))
+ [1.0, 2.0]
+ >>> vtor.check('float_list', [1, 2.0])
+ [1.0, 2.0]
+ >>> vtor.check('float_list', [1, 'a'])
+ Traceback (most recent call last):
+ VdtTypeError: the value "a" is of the wrong type.
+ """
+ return [is_float(mem) for mem in is_list(value, min, max)]
+
+
+def is_string_list(value, min=None, max=None):
+ """
+ Check that the value is a list of strings.
+
+ You can optionally specify the minimum and maximum number of members.
+
+ Each list member is checked that it is a string.
+
+ >>> vtor.check('string_list', ())
+ []
+ >>> vtor.check('string_list', [])
+ []
+ >>> vtor.check('string_list', ('a', 'b'))
+ ['a', 'b']
+ >>> vtor.check('string_list', ['a', 1])
+ Traceback (most recent call last):
+ VdtTypeError: the value "1" is of the wrong type.
+ >>> vtor.check('string_list', 'hello')
+ Traceback (most recent call last):
+ VdtTypeError: the value "hello" is of the wrong type.
+ """
+ if isinstance(value, StringTypes):
+ raise VdtTypeError(value)
+ return [is_string(mem) for mem in is_list(value, min, max)]
+
+
+def is_ip_addr_list(value, min=None, max=None):
+ """
+ Check that the value is a list of IP addresses.
+
+ You can optionally specify the minimum and maximum number of members.
+
+ Each list member is checked that it is an IP address.
+
+ >>> vtor.check('ip_addr_list', ())
+ []
+ >>> vtor.check('ip_addr_list', [])
+ []
+ >>> vtor.check('ip_addr_list', ('1.2.3.4', '5.6.7.8'))
+ ['1.2.3.4', '5.6.7.8']
+ >>> vtor.check('ip_addr_list', ['a'])
+ Traceback (most recent call last):
+ VdtValueError: the value "a" is unacceptable.
+ """
+ return [is_ip_addr(mem) for mem in is_list(value, min, max)]
+
+
+fun_dict = {
+ 'integer': is_integer,
+ 'float': is_float,
+ 'ip_addr': is_ip_addr,
+ 'string': is_string,
+ 'boolean': is_boolean,
+}
+
+
+def is_mixed_list(value, *args):
+ """
+ Check that the value is a list.
+ Allow specifying the type of each member.
+ Work on lists of specific lengths.
+
+ You specify each member as a positional argument specifying type
+
+ Each type should be one of the following strings :
+ 'integer', 'float', 'ip_addr', 'string', 'boolean'
+
+ So you can specify a list of two strings, followed by
+ two integers as :
+
+ mixed_list('string', 'string', 'integer', 'integer')
+
+ The length of the list must match the number of positional
+ arguments you supply.
+
+ >>> mix_str = "mixed_list('integer', 'float', 'ip_addr', 'string', 'boolean')"
+ >>> check_res = vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a', True))
+ >>> check_res == [1, 2.0, '1.2.3.4', 'a', True]
+ 1
+ >>> check_res = vtor.check(mix_str, ('1', '2.0', '1.2.3.4', 'a', 'True'))
+ >>> check_res == [1, 2.0, '1.2.3.4', 'a', True]
+ 1
+ >>> vtor.check(mix_str, ('b', 2.0, '1.2.3.4', 'a', True))
+ Traceback (most recent call last):
+ VdtTypeError: the value "b" is of the wrong type.
+ >>> vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a'))
+ Traceback (most recent call last):
+ VdtValueTooShortError: the value "(1, 2.0, '1.2.3.4', 'a')" is too short.
+ >>> vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a', 1, 'b'))
+ Traceback (most recent call last):
+ VdtValueTooLongError: the value "(1, 2.0, '1.2.3.4', 'a', 1, 'b')" is too long.
+ >>> vtor.check(mix_str, 0)
+ Traceback (most recent call last):
+ VdtTypeError: the value "0" is of the wrong type.
+
+ This test requires an elaborate setup, because of a change in error string
+ output from the interpreter between Python 2.2 and 2.3 .
+
+ >>> res_seq = (
+ ... 'passed an incorrect value "',
+ ... 'yoda',
+ ... '" for parameter "mixed_list".',
+ ... )
+ >>> if INTP_VER == (2, 2):
+ ... res_str = "".join(res_seq)
+ ... else:
+ ... res_str = "'".join(res_seq)
+ >>> try:
+ ... vtor.check('mixed_list("yoda")', ('a'))
+ ... except VdtParamError, err:
+ ... str(err) == res_str
+ 1
+ """
+ try:
+ length = len(value)
+ except TypeError:
+ raise VdtTypeError(value)
+ if length < len(args):
+ raise VdtValueTooShortError(value)
+ elif length > len(args):
+ raise VdtValueTooLongError(value)
+ try:
+ return [fun_dict[arg](val) for arg, val in zip(args, value)]
+ except KeyError, e:
+ raise VdtParamError('mixed_list', e)
+
+
+def is_option(value, *options):
+ """
+ This check matches the value to any of a set of options.
+
+ >>> vtor.check('option("yoda", "jedi")', 'yoda')
+ 'yoda'
+ >>> vtor.check('option("yoda", "jedi")', 'jed')
+ Traceback (most recent call last):
+ VdtValueError: the value "jed" is unacceptable.
+ >>> vtor.check('option("yoda", "jedi")', 0)
+ Traceback (most recent call last):
+ VdtTypeError: the value "0" is of the wrong type.
+ """
+ if not isinstance(value, StringTypes):
+ raise VdtTypeError(value)
+ if not value in options:
+ raise VdtValueError(value)
+ return value
+
+
+def _test(value, *args, **keywargs):
+ """
+ A function that exists for test purposes.
+
+ >>> checks = [
+ ... '3, 6, min=1, max=3, test=list(a, b, c)',
+ ... '3',
+ ... '3, 6',
+ ... '3,',
+ ... 'min=1, test="a b c"',
+ ... 'min=5, test="a, b, c"',
+ ... 'min=1, max=3, test="a, b, c"',
+ ... 'min=-100, test=-99',
+ ... 'min=1, max=3',
+ ... '3, 6, test="36"',
+ ... '3, 6, test="a, b, c"',
+ ... '3, max=3, test=list("a", "b", "c")',
+ ... '''3, max=3, test=list("'a'", 'b', "x=(c)")''',
+ ... "test='x=fish(3)'",
+ ... ]
+ >>> v = Validator({'test': _test})
+ >>> for entry in checks:
+ ... print v.check(('test(%s)' % entry), 3)
+ (3, ('3', '6'), {'test': ['a', 'b', 'c'], 'max': '3', 'min': '1'})
+ (3, ('3',), {})
+ (3, ('3', '6'), {})
+ (3, ('3',), {})
+ (3, (), {'test': 'a b c', 'min': '1'})
+ (3, (), {'test': 'a, b, c', 'min': '5'})
+ (3, (), {'test': 'a, b, c', 'max': '3', 'min': '1'})
+ (3, (), {'test': '-99', 'min': '-100'})
+ (3, (), {'max': '3', 'min': '1'})
+ (3, ('3', '6'), {'test': '36'})
+ (3, ('3', '6'), {'test': 'a, b, c'})
+ (3, ('3',), {'test': ['a', 'b', 'c'], 'max': '3'})
+ (3, ('3',), {'test': ["'a'", 'b', 'x=(c)'], 'max': '3'})
+ (3, (), {'test': 'x=fish(3)'})
+
+ >>> v = Validator()
+ >>> v.check('integer(default=6)', '3')
+ 3
+ >>> v.check('integer(default=6)', None, True)
+ 6
+ >>> v.get_default_value('integer(default=6)')
+ 6
+ >>> v.get_default_value('float(default=6)')
+ 6.0
+ >>> v.get_default_value('pass(default=None)')
+ >>> v.get_default_value("string(default='None')")
+ 'None'
+ >>> v.get_default_value('pass')
+ Traceback (most recent call last):
+ KeyError: 'Check "pass" has no default value.'
+ >>> v.get_default_value('pass(default=list(1, 2, 3, 4))')
+ ['1', '2', '3', '4']
+
+ >>> v = Validator()
+ >>> v.check("pass(default=None)", None, True)
+ >>> v.check("pass(default='None')", None, True)
+ 'None'
+ >>> v.check('pass(default="None")', None, True)
+ 'None'
+ >>> v.check('pass(default=list(1, 2, 3, 4))', None, True)
+ ['1', '2', '3', '4']
+
+ Bug test for unicode arguments
+ >>> v = Validator()
+ >>> v.check(u'string(min=4)', u'test')
+ u'test'
+
+ >>> v = Validator()
+ >>> v.get_default_value(u'string(min=4, default="1234")')
+ u'1234'
+ >>> v.check(u'string(min=4, default="1234")', u'test')
+ u'test'
+
+ >>> v = Validator()
+ >>> default = v.get_default_value('string(default=None)')
+ >>> default == None
+ 1
+ """
+ return (value, args, keywargs)
+
+
+def _test2():
+ """
+ >>>
+ >>> v = Validator()
+ >>> v.get_default_value('string(default="#ff00dd")')
+ '#ff00dd'
+ >>> v.get_default_value('integer(default=3) # comment')
+ 3
+ """
+
+
+if __name__ == '__main__':
+ # run the code tests in doctest format
+ import doctest
+ m = sys.modules.get('__main__')
+ globs = m.__dict__.copy()
+ globs.update({
+ 'INTP_VER': INTP_VER,
+ 'vtor': Validator(),
+ })
+ doctest.testmod(m, globs=globs)