/ RNS / vendor / configobj.py
configobj.py
   1  # configobj.py
   2  # A config file reader/writer that supports nested sections in config files.
   3  # Copyright (C) 2005-2014:
   4  # (name) : (email)
   5  # Michael Foord: fuzzyman AT voidspace DOT org DOT uk
   6  # Nicola Larosa: nico AT tekNico DOT net
   7  # Rob Dennis: rdennis AT gmail DOT com
   8  # Eli Courtwright: eli AT courtwright DOT org
   9  
  10  # This software is licensed under the terms of the BSD license.
  11  # http://opensource.org/licenses/BSD-3-Clause
  12  
  13  # ConfigObj 5 - main repository for documentation and issue tracking:
  14  # https://github.com/DiffSK/configobj
  15  
  16  import os
  17  import re
  18  import sys
  19  
  20  from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
  21  
  22  __version__ = '5.0.9'
  23  
  24  # imported lazily to avoid startup performance hit if it isn't used
  25  compiler = None
  26  
  27  # A dictionary mapping BOM to
  28  # the encoding to decode with, and what to set the
  29  # encoding attribute to.
  30  BOMS = {
  31      BOM_UTF8: ('utf_8', None),
  32      BOM_UTF16_BE: ('utf16_be', 'utf_16'),
  33      BOM_UTF16_LE: ('utf16_le', 'utf_16'),
  34      BOM_UTF16: ('utf_16', 'utf_16'),
  35      }
  36  # All legal variants of the BOM codecs.
  37  # TODO: the list of aliases is not meant to be exhaustive, is there a
  38  #   better way ?
  39  BOM_LIST = {
  40      'utf_16': 'utf_16',
  41      'u16': 'utf_16',
  42      'utf16': 'utf_16',
  43      'utf-16': 'utf_16',
  44      'utf16_be': 'utf16_be',
  45      'utf_16_be': 'utf16_be',
  46      'utf-16be': 'utf16_be',
  47      'utf16_le': 'utf16_le',
  48      'utf_16_le': 'utf16_le',
  49      'utf-16le': 'utf16_le',
  50      'utf_8': 'utf_8',
  51      'u8': 'utf_8',
  52      'utf': 'utf_8',
  53      'utf8': 'utf_8',
  54      'utf-8': 'utf_8',
  55      }
  56  
  57  # Map of encodings to the BOM to write.
  58  BOM_SET = {
  59      'utf_8': BOM_UTF8,
  60      'utf_16': BOM_UTF16,
  61      'utf16_be': BOM_UTF16_BE,
  62      'utf16_le': BOM_UTF16_LE,
  63      None: BOM_UTF8
  64      }
  65  
  66  
  67  def match_utf8(encoding):
  68      return BOM_LIST.get(encoding.lower()) == 'utf_8'
  69  
  70  
  71  # Quote strings used for writing values
  72  squot = "'%s'"
  73  dquot = '"%s"'
  74  noquot = "%s"
  75  wspace_plus = ' \r\n\v\t\'"'
  76  tsquot = '"""%s"""'
  77  tdquot = "'''%s'''"
  78  
  79  # Sentinel for use in getattr calls to replace hasattr
  80  MISSING = object()
  81  
  82  __all__ = (
  83      'DEFAULT_INDENT_TYPE',
  84      'DEFAULT_INTERPOLATION',
  85      'ConfigObjError',
  86      'NestingError',
  87      'ParseError',
  88      'DuplicateError',
  89      'ConfigspecError',
  90      'ConfigObj',
  91      'SimpleVal',
  92      'InterpolationError',
  93      'InterpolationLoopError',
  94      'MissingInterpolationOption',
  95      'RepeatSectionError',
  96      'ReloadError',
  97      'UnreprError',
  98      'UnknownType',
  99      'flatten_errors',
 100      'get_extra_values'
 101  )
 102  
 103  DEFAULT_INTERPOLATION = 'configparser'
 104  DEFAULT_INDENT_TYPE = '    '
 105  MAX_INTERPOL_DEPTH = 10
 106  
 107  OPTION_DEFAULTS = {
 108      'interpolation': True,
 109      'raise_errors': False,
 110      'list_values': True,
 111      'create_empty': False,
 112      'file_error': False,
 113      'configspec': None,
 114      'stringify': True,
 115      # option may be set to one of ('', ' ', '\t')
 116      'indent_type': None,
 117      'encoding': None,
 118      'default_encoding': None,
 119      'unrepr': False,
 120      'write_empty_values': False,
 121  }
 122  
 123  def getObj(s):
 124      global compiler
 125      if compiler is None:
 126          import compiler
 127      s = "a=" + s
 128      p = compiler.parse(s)
 129      return p.getChildren()[1].getChildren()[0].getChildren()[1]
 130  
 131  
 132  class UnknownType(Exception):
 133      pass
 134  
 135  
 136  class Builder(object):
 137      
 138      def build(self, o):
 139          if m is None:
 140              raise UnknownType(o.__class__.__name__)
 141          return m(o)
 142      
 143      def build_List(self, o):
 144          return list(map(self.build, o.getChildren()))
 145      
 146      def build_Const(self, o):
 147          return o.value
 148      
 149      def build_Dict(self, o):
 150          d = {}
 151          i = iter(map(self.build, o.getChildren()))
 152          for el in i:
 153              d[el] = next(i)
 154          return d
 155      
 156      def build_Tuple(self, o):
 157          return tuple(self.build_List(o))
 158      
 159      def build_Name(self, o):
 160          if o.name == 'None':
 161              return None
 162          if o.name == 'True':
 163              return True
 164          if o.name == 'False':
 165              return False
 166          
 167          # An undefined Name
 168          raise UnknownType('Undefined Name')
 169      
 170      def build_Add(self, o):
 171          real, imag = list(map(self.build_Const, o.getChildren()))
 172          try:
 173              real = float(real)
 174          except TypeError:
 175              raise UnknownType('Add')
 176          if not isinstance(imag, complex) or imag.real != 0.0:
 177              raise UnknownType('Add')
 178          return real+imag
 179      
 180      def build_Getattr(self, o):
 181          parent = self.build(o.expr)
 182          return getattr(parent, o.attrname)
 183      
 184      def build_UnarySub(self, o):
 185          return -self.build_Const(o.getChildren()[0])
 186      
 187      def build_UnaryAdd(self, o):
 188          return self.build_Const(o.getChildren()[0])
 189  
 190  
 191  _builder = Builder()
 192  
 193  
 194  def unrepr(s):
 195      if not s:
 196          return s
 197      
 198      # this is supposed to be safe
 199      import ast
 200      return ast.literal_eval(s)
 201  
 202  
 203  class ConfigObjError(SyntaxError):
 204      """
 205      This is the base class for all errors that ConfigObj raises.
 206      It is a subclass of SyntaxError.
 207      """
 208      def __init__(self, message='', line_number=None, line=''):
 209          self.line = line
 210          self.line_number = line_number
 211          SyntaxError.__init__(self, message)
 212  
 213  
 214  class NestingError(ConfigObjError):
 215      """
 216      This error indicates a level of nesting that doesn't match.
 217      """
 218  
 219  
 220  class ParseError(ConfigObjError):
 221      """
 222      This error indicates that a line is badly written.
 223      It is neither a valid ``key = value`` line,
 224      nor a valid section marker line.
 225      """
 226  
 227  
 228  class ReloadError(IOError):
 229      """
 230      A 'reload' operation failed.
 231      This exception is a subclass of ``IOError``.
 232      """
 233      def __init__(self):
 234          IOError.__init__(self, 'reload failed, filename is not set.')
 235  
 236  
 237  class DuplicateError(ConfigObjError):
 238      """
 239      The keyword or section specified already exists.
 240      """
 241  
 242  
 243  class ConfigspecError(ConfigObjError):
 244      """
 245      An error occured whilst parsing a configspec.
 246      """
 247  
 248  
 249  class InterpolationError(ConfigObjError):
 250      """Base class for the two interpolation errors."""
 251  
 252  
 253  class InterpolationLoopError(InterpolationError):
 254      """Maximum interpolation depth exceeded in string interpolation."""
 255  
 256      def __init__(self, option):
 257          InterpolationError.__init__(
 258              self,
 259              'interpolation loop detected in value "%s".' % option)
 260  
 261  
 262  class RepeatSectionError(ConfigObjError):
 263      """
 264      This error indicates additional sections in a section with a
 265      ``__many__`` (repeated) section.
 266      """
 267  
 268  
 269  class MissingInterpolationOption(InterpolationError):
 270      """A value specified for interpolation was missing."""
 271      def __init__(self, option):
 272          msg = 'missing option "%s" in interpolation.' % option
 273          InterpolationError.__init__(self, msg)
 274  
 275  
 276  class UnreprError(ConfigObjError):
 277      """An error parsing in unrepr mode."""
 278  
 279  
 280  
 281  class InterpolationEngine(object):
 282      """
 283      A helper class to help perform string interpolation.
 284  
 285      This class is an abstract base class; its descendants perform
 286      the actual work.
 287      """
 288  
 289      # compiled regexp to use in self.interpolate()
 290      _KEYCRE = re.compile(r"%\(([^)]*)\)s")
 291      _cookie = '%'
 292  
 293      def __init__(self, section):
 294          # the Section instance that "owns" this engine
 295          self.section = section
 296  
 297  
 298      def interpolate(self, key, value):
 299          # short-cut
 300          if not self._cookie in value:
 301              return value
 302          
 303          def recursive_interpolate(key, value, section, backtrail):
 304              """The function that does the actual work.
 305  
 306              ``value``: the string we're trying to interpolate.
 307              ``section``: the section in which that string was found
 308              ``backtrail``: a dict to keep track of where we've been,
 309              to detect and prevent infinite recursion loops
 310  
 311              This is similar to a depth-first-search algorithm.
 312              """
 313              # Have we been here already?
 314              if (key, section.name) in backtrail:
 315                  # Yes - infinite loop detected
 316                  raise InterpolationLoopError(key)
 317              # Place a marker on our backtrail so we won't come back here again
 318              backtrail[(key, section.name)] = 1
 319  
 320              # Now start the actual work
 321              match = self._KEYCRE.search(value)
 322              while match:
 323                  # The actual parsing of the match is implementation-dependent,
 324                  # so delegate to our helper function
 325                  k, v, s = self._parse_match(match)
 326                  if k is None:
 327                      # That's the signal that no further interpolation is needed
 328                      replacement = v
 329                  else:
 330                      # Further interpolation may be needed to obtain final value
 331                      replacement = recursive_interpolate(k, v, s, backtrail)
 332                  # Replace the matched string with its final value
 333                  start, end = match.span()
 334                  value = ''.join((value[:start], replacement, value[end:]))
 335                  new_search_start = start + len(replacement)
 336                  # Pick up the next interpolation key, if any, for next time
 337                  # through the while loop
 338                  match = self._KEYCRE.search(value, new_search_start)
 339  
 340              # Now safe to come back here again; remove marker from backtrail
 341              del backtrail[(key, section.name)]
 342  
 343              return value
 344  
 345          # Back in interpolate(), all we have to do is kick off the recursive
 346          # function with appropriate starting values
 347          value = recursive_interpolate(key, value, self.section, {})
 348          return value
 349  
 350  
 351      def _fetch(self, key):
 352          """Helper function to fetch values from owning section.
 353  
 354          Returns a 2-tuple: the value, and the section where it was found.
 355          """
 356          # switch off interpolation before we try and fetch anything !
 357          save_interp = self.section.main.interpolation
 358          self.section.main.interpolation = False
 359  
 360          # Start at section that "owns" this InterpolationEngine
 361          current_section = self.section
 362          while True:
 363              # try the current section first
 364              val = current_section.get(key)
 365              if val is not None and not isinstance(val, Section):
 366                  break
 367              # try "DEFAULT" next
 368              val = current_section.get('DEFAULT', {}).get(key)
 369              if val is not None and not isinstance(val, Section):
 370                  break
 371              # move up to parent and try again
 372              # top-level's parent is itself
 373              if current_section.parent is current_section:
 374                  # reached top level, time to give up
 375                  break
 376              current_section = current_section.parent
 377  
 378          # restore interpolation to previous value before returning
 379          self.section.main.interpolation = save_interp
 380          if val is None:
 381              raise MissingInterpolationOption(key)
 382          return val, current_section
 383  
 384  
 385      def _parse_match(self, match):
 386          """Implementation-dependent helper function.
 387  
 388          Will be passed a match object corresponding to the interpolation
 389          key we just found (e.g., "%(foo)s" or "$foo"). Should look up that
 390          key in the appropriate config file section (using the ``_fetch()``
 391          helper function) and return a 3-tuple: (key, value, section)
 392  
 393          ``key`` is the name of the key we're looking for
 394          ``value`` is the value found for that key
 395          ``section`` is a reference to the section where it was found
 396  
 397          ``key`` and ``section`` should be None if no further
 398          interpolation should be performed on the resulting value
 399          (e.g., if we interpolated "$$" and returned "$").
 400          """
 401          raise NotImplementedError()
 402      
 403  
 404  
 405  class ConfigParserInterpolation(InterpolationEngine):
 406      """Behaves like ConfigParser."""
 407      _cookie = '%'
 408      _KEYCRE = re.compile(r"%\(([^)]*)\)s")
 409  
 410      def _parse_match(self, match):
 411          key = match.group(1)
 412          value, section = self._fetch(key)
 413          return key, value, section
 414  
 415  
 416  
 417  class TemplateInterpolation(InterpolationEngine):
 418      """Behaves like string.Template."""
 419      _cookie = '$'
 420      _delimiter = '$'
 421      _KEYCRE = re.compile(r"""
 422          \$(?:
 423            (?P<escaped>\$)              |   # Two $ signs
 424            (?P<named>[_a-z][_a-z0-9]*)  |   # $name format
 425            {(?P<braced>[^}]*)}              # ${name} format
 426          )
 427          """, re.IGNORECASE | re.VERBOSE)
 428  
 429      def _parse_match(self, match):
 430          # Valid name (in or out of braces): fetch value from section
 431          key = match.group('named') or match.group('braced')
 432          if key is not None:
 433              value, section = self._fetch(key)
 434              return key, value, section
 435          # Escaped delimiter (e.g., $$): return single delimiter
 436          if match.group('escaped') is not None:
 437              # Return None for key and section to indicate it's time to stop
 438              return None, self._delimiter, None
 439          # Anything else: ignore completely, just return it unchanged
 440          return None, match.group(), None
 441  
 442  
 443  interpolation_engines = {
 444      'configparser': ConfigParserInterpolation,
 445      'template': TemplateInterpolation,
 446  }
 447  
 448  
 449  def __newobj__(cls, *args):
 450      # Hack for pickle
 451      return cls.__new__(cls, *args) 
 452  
 453  class Section(dict):
 454      """
 455      A dictionary-like object that represents a section in a config file.
 456      
 457      It does string interpolation if the 'interpolation' attribute
 458      of the 'main' object is set to True.
 459      
 460      Interpolation is tried first from this object, then from the 'DEFAULT'
 461      section of this object, next from the parent and its 'DEFAULT' section,
 462      and so on until the main object is reached.
 463      
 464      A Section will behave like an ordered dictionary - following the
 465      order of the ``scalars`` and ``sections`` attributes.
 466      You can use this to change the order of members.
 467      
 468      Iteration follows the order: scalars, then sections.
 469      """
 470  
 471      
 472      def __setstate__(self, state):
 473          dict.update(self, state[0])
 474          self.__dict__.update(state[1])
 475  
 476      def __reduce__(self):
 477          state = (dict(self), self.__dict__)
 478          return (__newobj__, (self.__class__,), state)
 479      
 480      
 481      def __init__(self, parent, depth, main, indict=None, name=None):
 482          """
 483          * parent is the section above
 484          * depth is the depth level of this section
 485          * main is the main ConfigObj
 486          * indict is a dictionary to initialise the section with
 487          """
 488          if indict is None:
 489              indict = {}
 490          dict.__init__(self)
 491          # used for nesting level *and* interpolation
 492          self.parent = parent
 493          # used for the interpolation attribute
 494          self.main = main
 495          # level of nesting depth of this Section
 496          self.depth = depth
 497          # purely for information
 498          self.name = name
 499          #
 500          self._initialise()
 501          # we do this explicitly so that __setitem__ is used properly
 502          # (rather than just passing to ``dict.__init__``)
 503          for entry, value in indict.items():
 504              self[entry] = value
 505              
 506              
 507      def _initialise(self):
 508          # the sequence of scalar values in this Section
 509          self.scalars = []
 510          # the sequence of sections in this Section
 511          self.sections = []
 512          # for comments :-)
 513          self.comments = {}
 514          self.inline_comments = {}
 515          # the configspec
 516          self.configspec = None
 517          # for defaults
 518          self.defaults = []
 519          self.default_values = {}
 520          self.extra_values = []
 521          self._created = False
 522  
 523  
 524      def _interpolate(self, key, value):
 525          try:
 526              # do we already have an interpolation engine?
 527              engine = self._interpolation_engine
 528          except AttributeError:
 529              # not yet: first time running _interpolate(), so pick the engine
 530              name = self.main.interpolation
 531              if name == True:  # note that "if name:" would be incorrect here
 532                  # backwards-compatibility: interpolation=True means use default
 533                  name = DEFAULT_INTERPOLATION
 534              name = name.lower()  # so that "Template", "template", etc. all work
 535              class_ = interpolation_engines.get(name, None)
 536              if class_ is None:
 537                  # invalid value for self.main.interpolation
 538                  self.main.interpolation = False
 539                  return value
 540              else:
 541                  # save reference to engine so we don't have to do this again
 542                  engine = self._interpolation_engine = class_(self)
 543          # let the engine do the actual work
 544          return engine.interpolate(key, value)
 545  
 546  
 547      def __getitem__(self, key):
 548          """Fetch the item and do string interpolation."""
 549          val = dict.__getitem__(self, key)
 550          if self.main.interpolation: 
 551              if isinstance(val, str):
 552                  return self._interpolate(key, val)
 553              if isinstance(val, list):
 554                  def _check(entry):
 555                      if isinstance(entry, str):
 556                          return self._interpolate(key, entry)
 557                      return entry
 558                  new = [_check(entry) for entry in val]
 559                  if new != val:
 560                      return new
 561          return val
 562  
 563  
 564      def __setitem__(self, key, value, unrepr=False):
 565          """
 566          Correctly set a value.
 567          
 568          Making dictionary values Section instances.
 569          (We have to special case 'Section' instances - which are also dicts)
 570          
 571          Keys must be strings.
 572          Values need only be strings (or lists of strings) if
 573          ``main.stringify`` is set.
 574          
 575          ``unrepr`` must be set when setting a value to a dictionary, without
 576          creating a new sub-section.
 577          """
 578          if not isinstance(key, str):
 579              raise ValueError('The key "%s" is not a string.' % key)
 580          
 581          # add the comment
 582          if key not in self.comments:
 583              self.comments[key] = []
 584              self.inline_comments[key] = ''
 585          # remove the entry from defaults
 586          if key in self.defaults:
 587              self.defaults.remove(key)
 588          #
 589          if isinstance(value, Section):
 590              if key not in self:
 591                  self.sections.append(key)
 592              dict.__setitem__(self, key, value)
 593          elif isinstance(value, dict) and not unrepr:
 594              # First create the new depth level,
 595              # then create the section
 596              if key not in self:
 597                  self.sections.append(key)
 598              new_depth = self.depth + 1
 599              dict.__setitem__(
 600                  self,
 601                  key,
 602                  Section(
 603                      self,
 604                      new_depth,
 605                      self.main,
 606                      indict=value,
 607                      name=key))
 608          else:
 609              if key not in self:
 610                  self.scalars.append(key)
 611              if not self.main.stringify:
 612                  if isinstance(value, str):
 613                      pass
 614                  elif isinstance(value, (list, tuple)):
 615                      for entry in value:
 616                          if not isinstance(entry, str):
 617                              raise TypeError('Value is not a string "%s".' % entry)
 618                  else:
 619                      raise TypeError('Value is not a string "%s".' % value)
 620              dict.__setitem__(self, key, value)
 621  
 622  
 623      def __delitem__(self, key):
 624          """Remove items from the sequence when deleting."""
 625          dict. __delitem__(self, key)
 626          if key in self.scalars:
 627              self.scalars.remove(key)
 628          else:
 629              self.sections.remove(key)
 630          del self.comments[key]
 631          del self.inline_comments[key]
 632  
 633  
 634      def get(self, key, default=None):
 635          """A version of ``get`` that doesn't bypass string interpolation."""
 636          try:
 637              return self[key]
 638          except KeyError:
 639              return default
 640  
 641  
 642      def update(self, indict):
 643          """
 644          A version of update that uses our ``__setitem__``.
 645          """
 646          for entry in indict:
 647              self[entry] = indict[entry]
 648  
 649  
 650      def pop(self, key, default=MISSING):
 651          """
 652          'D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
 653          If key is not found, d is returned if given, otherwise KeyError is raised'
 654          """
 655          try:
 656              val = self[key]
 657          except KeyError:
 658              if default is MISSING:
 659                  raise
 660              val = default
 661          else:
 662              del self[key]
 663          return val
 664  
 665  
 666      def popitem(self):
 667          """Pops the first (key,val)"""
 668          sequence = (self.scalars + self.sections)
 669          if not sequence:
 670              raise KeyError(": 'popitem(): dictionary is empty'")
 671          key = sequence[0]
 672          val =  self[key]
 673          del self[key]
 674          return key, val
 675  
 676  
 677      def clear(self):
 678          """
 679          A version of clear that also affects scalars/sections
 680          Also clears comments and configspec.
 681          
 682          Leaves other attributes alone :
 683              depth/main/parent are not affected
 684          """
 685          dict.clear(self)
 686          self.scalars = []
 687          self.sections = []
 688          self.comments = {}
 689          self.inline_comments = {}
 690          self.configspec = None
 691          self.defaults = []
 692          self.extra_values = []
 693  
 694  
 695      def setdefault(self, key, default=None):
 696          """A version of setdefault that sets sequence if appropriate."""
 697          try:
 698              return self[key]
 699          except KeyError:
 700              self[key] = default
 701              return self[key]
 702  
 703  
 704      def items(self):
 705          """D.items() -> list of D's (key, value) pairs, as 2-tuples"""
 706          return list(zip((self.scalars + self.sections), list(self.values())))
 707  
 708  
 709      def keys(self):
 710          """D.keys() -> list of D's keys"""
 711          return (self.scalars + self.sections)
 712  
 713  
 714      def values(self):
 715          """D.values() -> list of D's values"""
 716          return [self[key] for key in (self.scalars + self.sections)]
 717  
 718  
 719      def iteritems(self):
 720          """D.iteritems() -> an iterator over the (key, value) items of D"""
 721          return iter(list(self.items()))
 722  
 723  
 724      def iterkeys(self):
 725          """D.iterkeys() -> an iterator over the keys of D"""
 726          return iter((self.scalars + self.sections))
 727  
 728      __iter__ = iterkeys
 729  
 730  
 731      def itervalues(self):
 732          """D.itervalues() -> an iterator over the values of D"""
 733          return iter(list(self.values()))
 734  
 735  
 736      def __repr__(self):
 737          """x.__repr__() <==> repr(x)"""
 738          def _getval(key):
 739              try:
 740                  return self[key]
 741              except MissingInterpolationOption:
 742                  return dict.__getitem__(self, key)
 743          return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(_getval(key))))
 744              for key in (self.scalars + self.sections)])
 745  
 746      __str__ = __repr__
 747      __str__.__doc__ = "x.__str__() <==> str(x)"
 748  
 749  
 750      # Extra methods - not in a normal dictionary
 751  
 752      def dict(self):
 753          """
 754          Return a deepcopy of self as a dictionary.
 755          
 756          All members that are ``Section`` instances are recursively turned to
 757          ordinary dictionaries - by calling their ``dict`` method.
 758          
 759          >>> n = a.dict()
 760          >>> n == a
 761          1
 762          >>> n is a
 763          0
 764          """
 765          newdict = {}
 766          for entry in self:
 767              this_entry = self[entry]
 768              if isinstance(this_entry, Section):
 769                  this_entry = this_entry.dict()
 770              elif isinstance(this_entry, list):
 771                  # create a copy rather than a reference
 772                  this_entry = list(this_entry)
 773              elif isinstance(this_entry, tuple):
 774                  # create a copy rather than a reference
 775                  this_entry = tuple(this_entry)
 776              newdict[entry] = this_entry
 777          return newdict
 778  
 779  
 780      def merge(self, indict):
 781          """
 782          A recursive update - useful for merging config files.
 783          
 784          >>> a = '''[section1]
 785          ...     option1 = True
 786          ...     [[subsection]]
 787          ...     more_options = False
 788          ...     # end of file'''.splitlines()
 789          >>> b = '''# File is user.ini
 790          ...     [section1]
 791          ...     option1 = False
 792          ...     # end of file'''.splitlines()
 793          >>> c1 = ConfigObj(b)
 794          >>> c2 = ConfigObj(a)
 795          >>> c2.merge(c1)
 796          >>> c2
 797          ConfigObj({'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}})
 798          """
 799          for key, val in list(indict.items()):
 800              if (key in self and isinstance(self[key], dict) and
 801                                  isinstance(val, dict)):
 802                  self[key].merge(val)
 803              else:   
 804                  self[key] = val
 805  
 806  
 807      def rename(self, oldkey, newkey):
 808          """
 809          Change a keyname to another, without changing position in sequence.
 810          
 811          Implemented so that transformations can be made on keys,
 812          as well as on values. (used by encode and decode)
 813          
 814          Also renames comments.
 815          """
 816          if oldkey in self.scalars:
 817              the_list = self.scalars
 818          elif oldkey in self.sections:
 819              the_list = self.sections
 820          else:
 821              raise KeyError('Key "%s" not found.' % oldkey)
 822          pos = the_list.index(oldkey)
 823          #
 824          val = self[oldkey]
 825          dict.__delitem__(self, oldkey)
 826          dict.__setitem__(self, newkey, val)
 827          the_list.remove(oldkey)
 828          the_list.insert(pos, newkey)
 829          comm = self.comments[oldkey]
 830          inline_comment = self.inline_comments[oldkey]
 831          del self.comments[oldkey]
 832          del self.inline_comments[oldkey]
 833          self.comments[newkey] = comm
 834          self.inline_comments[newkey] = inline_comment
 835  
 836  
 837      def walk(self, function, raise_errors=True,
 838              call_on_sections=False, **keywargs):
 839          """
 840          Walk every member and call a function on the keyword and value.
 841          
 842          Return a dictionary of the return values
 843          
 844          If the function raises an exception, raise the errror
 845          unless ``raise_errors=False``, in which case set the return value to
 846          ``False``.
 847          
 848          Any unrecognised keyword arguments you pass to walk, will be pased on
 849          to the function you pass in.
 850          
 851          Note: if ``call_on_sections`` is ``True`` then - on encountering a
 852          subsection, *first* the function is called for the *whole* subsection,
 853          and then recurses into it's members. This means your function must be
 854          able to handle strings, dictionaries and lists. This allows you
 855          to change the key of subsections as well as for ordinary members. The
 856          return value when called on the whole subsection has to be discarded.
 857          
 858          See  the encode and decode methods for examples, including functions.
 859          
 860          .. admonition:: caution
 861          
 862              You can use ``walk`` to transform the names of members of a section
 863              but you mustn't add or delete members.
 864          
 865          >>> config = '''[XXXXsection]
 866          ... XXXXkey = XXXXvalue'''.splitlines()
 867          >>> cfg = ConfigObj(config)
 868          >>> cfg
 869          ConfigObj({'XXXXsection': {'XXXXkey': 'XXXXvalue'}})
 870          >>> def transform(section, key):
 871          ...     val = section[key]
 872          ...     newkey = key.replace('XXXX', 'CLIENT1')
 873          ...     section.rename(key, newkey)
 874          ...     if isinstance(val, (tuple, list, dict)):
 875          ...         pass
 876          ...     else:
 877          ...         val = val.replace('XXXX', 'CLIENT1')
 878          ...         section[newkey] = val
 879          >>> cfg.walk(transform, call_on_sections=True)
 880          {'CLIENT1section': {'CLIENT1key': None}}
 881          >>> cfg
 882          ConfigObj({'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}})
 883          """
 884          out = {}
 885          # scalars first
 886          for i in range(len(self.scalars)):
 887              entry = self.scalars[i]
 888              try:
 889                  val = function(self, entry, **keywargs)
 890                  # bound again in case name has changed
 891                  entry = self.scalars[i]
 892                  out[entry] = val
 893              except Exception:
 894                  if raise_errors:
 895                      raise
 896                  else:
 897                      entry = self.scalars[i]
 898                      out[entry] = False
 899          # then sections
 900          for i in range(len(self.sections)):
 901              entry = self.sections[i]
 902              if call_on_sections:
 903                  try:
 904                      function(self, entry, **keywargs)
 905                  except Exception:
 906                      if raise_errors:
 907                          raise
 908                      else:
 909                          entry = self.sections[i]
 910                          out[entry] = False
 911                  # bound again in case name has changed
 912                  entry = self.sections[i]
 913              # previous result is discarded
 914              out[entry] = self[entry].walk(
 915                  function,
 916                  raise_errors=raise_errors,
 917                  call_on_sections=call_on_sections,
 918                  **keywargs)
 919          return out
 920  
 921  
 922      def as_bool(self, key):
 923          """
 924          Accepts a key as input. The corresponding value must be a string or
 925          the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to
 926          retain compatibility with Python 2.2.
 927          
 928          If the string is one of  ``True``, ``On``, ``Yes``, or ``1`` it returns 
 929          ``True``.
 930          
 931          If the string is one of  ``False``, ``Off``, ``No``, or ``0`` it returns 
 932          ``False``.
 933          
 934          ``as_bool`` is not case sensitive.
 935          
 936          Any other input will raise a ``ValueError``.
 937          
 938          >>> a = ConfigObj()
 939          >>> a['a'] = 'fish'
 940          >>> a.as_bool('a')
 941          Traceback (most recent call last):
 942          ValueError: Value "fish" is neither True nor False
 943          >>> a['b'] = 'True'
 944          >>> a.as_bool('b')
 945          1
 946          >>> a['b'] = 'off'
 947          >>> a.as_bool('b')
 948          0
 949          """
 950          val = self[key]
 951          if val == True:
 952              return True
 953          elif val == False:
 954              return False
 955          else:
 956              try:
 957                  if not isinstance(val, str):
 958                      # TODO: Why do we raise a KeyError here?
 959                      raise KeyError()
 960                  else:
 961                      return self.main._bools[val.lower()]
 962              except KeyError:
 963                  raise ValueError('Value "%s" is neither True nor False' % val)
 964  
 965  
 966      def as_int(self, key):
 967          """
 968          A convenience method which coerces the specified value to an integer.
 969          
 970          If the value is an invalid literal for ``int``, a ``ValueError`` will
 971          be raised.
 972          
 973          >>> a = ConfigObj()
 974          >>> a['a'] = 'fish'
 975          >>> a.as_int('a')
 976          Traceback (most recent call last):
 977          ValueError: invalid literal for int() with base 10: 'fish'
 978          >>> a['b'] = '1'
 979          >>> a.as_int('b')
 980          1
 981          >>> a['b'] = '3.2'
 982          >>> a.as_int('b')
 983          Traceback (most recent call last):
 984          ValueError: invalid literal for int() with base 10: '3.2'
 985          """
 986          return int(self[key])
 987  
 988  
 989      def as_float(self, key):
 990          """
 991          A convenience method which coerces the specified value to a float.
 992          
 993          If the value is an invalid literal for ``float``, a ``ValueError`` will
 994          be raised.
 995          
 996          >>> a = ConfigObj()
 997          >>> a['a'] = 'fish'
 998          >>> a.as_float('a')  #doctest: +IGNORE_EXCEPTION_DETAIL
 999          Traceback (most recent call last):
1000          ValueError: invalid literal for float(): fish
1001          >>> a['b'] = '1'
1002          >>> a.as_float('b')
1003          1.0
1004          >>> a['b'] = '3.2'
1005          >>> a.as_float('b')  #doctest: +ELLIPSIS
1006          3.2...
1007          """
1008          return float(self[key])
1009      
1010      
1011      def as_list(self, key):
1012          """
1013          A convenience method which fetches the specified value, guaranteeing
1014          that it is a list.
1015          
1016          >>> a = ConfigObj()
1017          >>> a['a'] = 1
1018          >>> a.as_list('a')
1019          [1]
1020          >>> a['a'] = (1,)
1021          >>> a.as_list('a')
1022          [1]
1023          >>> a['a'] = [1]
1024          >>> a.as_list('a')
1025          [1]
1026          """
1027          result = self[key]
1028          if isinstance(result, (tuple, list)):
1029              return list(result)
1030          return [result]
1031          
1032  
1033      def restore_default(self, key):
1034          """
1035          Restore (and return) default value for the specified key.
1036          
1037          This method will only work for a ConfigObj that was created
1038          with a configspec and has been validated.
1039          
1040          If there is no default value for this key, ``KeyError`` is raised.
1041          """
1042          default = self.default_values[key]
1043          dict.__setitem__(self, key, default)
1044          if key not in self.defaults:
1045              self.defaults.append(key)
1046          return default
1047  
1048      
1049      def restore_defaults(self):
1050          """
1051          Recursively restore default values to all members
1052          that have them.
1053          
1054          This method will only work for a ConfigObj that was created
1055          with a configspec and has been validated.
1056          
1057          It doesn't delete or modify entries without default values.
1058          """
1059          for key in self.default_values:
1060              self.restore_default(key)
1061              
1062          for section in self.sections:
1063              self[section].restore_defaults()
1064  
1065  
1066  class ConfigObj(Section):
1067      """An object to read, create, and write config files."""
1068  
1069      _keyword = re.compile(r'''^ # line start
1070          (\s*)                   # indentation
1071          (                       # keyword
1072              (?:".*?")|          # double quotes
1073              (?:'.*?')|          # single quotes
1074              (?:[^'"=].*?)       # no quotes
1075          )
1076          \s*=\s*                 # divider
1077          (.*)                    # value (including list values and comments)
1078          $   # line end
1079          ''',
1080          re.VERBOSE)
1081  
1082      _sectionmarker = re.compile(r'''^
1083          (\s*)                     # 1: indentation
1084          ((?:\[\s*)+)              # 2: section marker open
1085          (                         # 3: section name open
1086              (?:"\s*\S.*?\s*")|    # at least one non-space with double quotes
1087              (?:'\s*\S.*?\s*')|    # at least one non-space with single quotes
1088              (?:[^'"\s].*?)        # at least one non-space unquoted
1089          )                         # section name close
1090          ((?:\s*\])+)              # 4: section marker close
1091          \s*(\#.*)?                # 5: optional comment
1092          $''',
1093          re.VERBOSE)
1094  
1095      # this regexp pulls list values out as a single string
1096      # or single values and comments
1097      # FIXME: this regex adds a '' to the end of comma terminated lists
1098      #   workaround in ``_handle_value``
1099      _valueexp = re.compile(r'''^
1100          (?:
1101              (?:
1102                  (
1103                      (?:
1104                          (?:
1105                              (?:".*?")|              # double quotes
1106                              (?:'.*?')|              # single quotes
1107                              (?:[^'",\#][^,\#]*?)    # unquoted
1108                          )
1109                          \s*,\s*                     # comma
1110                      )*      # match all list items ending in a comma (if any)
1111                  )
1112                  (
1113                      (?:".*?")|                      # double quotes
1114                      (?:'.*?')|                      # single quotes
1115                      (?:[^'",\#\s][^,]*?)|           # unquoted
1116                      (?:(?<!,))                      # Empty value
1117                  )?          # last item in a list - or string value
1118              )|
1119              (,)             # alternatively a single comma - empty list
1120          )
1121          \s*(\#.*)?          # optional comment
1122          $''',
1123          re.VERBOSE)
1124  
1125      # use findall to get the members of a list value
1126      _listvalueexp = re.compile(r'''
1127          (
1128              (?:".*?")|          # double quotes
1129              (?:'.*?')|          # single quotes
1130              (?:[^'",\#]?.*?)       # unquoted
1131          )
1132          \s*,\s*                 # comma
1133          ''',
1134          re.VERBOSE)
1135  
1136      # this regexp is used for the value
1137      # when lists are switched off
1138      _nolistvalue = re.compile(r'''^
1139          (
1140              (?:".*?")|          # double quotes
1141              (?:'.*?')|          # single quotes
1142              (?:[^'"\#].*?)|     # unquoted
1143              (?:)                # Empty value
1144          )
1145          \s*(\#.*)?              # optional comment
1146          $''',
1147          re.VERBOSE)
1148  
1149      # regexes for finding triple quoted values on one line
1150      _single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$")
1151      _single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$')
1152      _multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$")
1153      _multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$')
1154  
1155      _triple_quote = {
1156          "'''": (_single_line_single, _multi_line_single),
1157          '"""': (_single_line_double, _multi_line_double),
1158      }
1159  
1160      # Used by the ``istrue`` Section method
1161      _bools = {
1162          'yes': True, 'no': False,
1163          'on': True, 'off': False,
1164          '1': True, '0': False,
1165          'true': True, 'false': False,
1166          }
1167  
1168  
1169      def __init__(self, infile=None, options=None, configspec=None, encoding=None,
1170                   interpolation=True, raise_errors=False, list_values=True,
1171                   create_empty=False, file_error=False, stringify=True,
1172                   indent_type=None, default_encoding=None, unrepr=False,
1173                   write_empty_values=False, _inspec=False):
1174          """
1175          Parse a config file or create a config file object.
1176          
1177          ``ConfigObj(infile=None, configspec=None, encoding=None,
1178                      interpolation=True, raise_errors=False, list_values=True,
1179                      create_empty=False, file_error=False, stringify=True,
1180                      indent_type=None, default_encoding=None, unrepr=False,
1181                      write_empty_values=False, _inspec=False)``
1182          """
1183          self._inspec = _inspec
1184          # init the superclass
1185          Section.__init__(self, self, 0, self)
1186          
1187          infile = infile or []
1188          
1189          _options = {'configspec': configspec,
1190                      'encoding': encoding, 'interpolation': interpolation,
1191                      'raise_errors': raise_errors, 'list_values': list_values,
1192                      'create_empty': create_empty, 'file_error': file_error,
1193                      'stringify': stringify, 'indent_type': indent_type,
1194                      'default_encoding': default_encoding, 'unrepr': unrepr,
1195                      'write_empty_values': write_empty_values}
1196  
1197          if options is None:
1198              options = _options
1199          else:
1200              import warnings
1201              warnings.warn('Passing in an options dictionary to ConfigObj() is '
1202                            'deprecated. Use **options instead.',
1203                            DeprecationWarning, stacklevel=2)
1204              
1205              # TODO: check the values too.
1206              for entry in options:
1207                  if entry not in OPTION_DEFAULTS:
1208                      raise TypeError('Unrecognised option "%s".' % entry)
1209              for entry, value in list(OPTION_DEFAULTS.items()):
1210                  if entry not in options:
1211                      options[entry] = value
1212                  keyword_value = _options[entry]
1213                  if value != keyword_value:
1214                      options[entry] = keyword_value
1215          
1216          # XXXX this ignores an explicit list_values = True in combination
1217          # with _inspec. The user should *never* do that anyway, but still...
1218          if _inspec:
1219              options['list_values'] = False
1220          
1221          self._initialise(options)
1222          configspec = options['configspec']
1223          self._original_configspec = configspec
1224          self._load(infile, configspec)
1225          
1226          
1227      def _load(self, infile, configspec):
1228          if isinstance(infile, str):
1229              self.filename = infile
1230              if os.path.isfile(infile):
1231                  with open(infile, 'rb') as h:
1232                      content = h.readlines() or []
1233              elif self.file_error:
1234                  # raise an error if the file doesn't exist
1235                  raise IOError('Config file not found: "%s".' % self.filename)
1236              else:
1237                  # file doesn't already exist
1238                  if self.create_empty:
1239                      # this is a good test that the filename specified
1240                      # isn't impossible - like on a non-existent device
1241                      with open(infile, 'w') as h:
1242                          h.write('')
1243                  content = []
1244                  
1245          elif isinstance(infile, (list, tuple)):
1246              content = list(infile)
1247              
1248          elif isinstance(infile, dict):
1249              # initialise self
1250              # the Section class handles creating subsections
1251              if isinstance(infile, ConfigObj):
1252                  # get a copy of our ConfigObj
1253                  def set_section(in_section, this_section):
1254                      for entry in in_section.scalars:
1255                          this_section[entry] = in_section[entry]
1256                      for section in in_section.sections:
1257                          this_section[section] = {}
1258                          set_section(in_section[section], this_section[section])
1259                  set_section(infile, self)
1260                  
1261              else:
1262                  for entry in infile:
1263                      self[entry] = infile[entry]
1264              del self._errors
1265              
1266              if configspec is not None:
1267                  self._handle_configspec(configspec)
1268              else:
1269                  self.configspec = None
1270              return
1271          
1272          elif getattr(infile, 'read', MISSING) is not MISSING:
1273              # This supports file like objects
1274              content = infile.read() or []
1275              # needs splitting into lines - but needs doing *after* decoding
1276              # in case it's not an 8 bit encoding
1277          else:
1278              raise TypeError('infile must be a filename, file like object, or list of lines.')
1279  
1280          if content:
1281              # don't do it for the empty ConfigObj
1282              content = self._handle_bom(content)
1283              # infile is now *always* a list
1284              #
1285              # Set the newlines attribute (first line ending it finds)
1286              # and strip trailing '\n' or '\r' from lines
1287              for line in content:
1288                  if (not line) or (line[-1] not in ('\r', '\n')):
1289                      continue
1290                  for end in ('\r\n', '\n', '\r'):
1291                      if line.endswith(end):
1292                          self.newlines = end
1293                          break
1294                  break
1295  
1296          assert all(isinstance(line, str) for line in content), repr(content)
1297          content = [line.rstrip('\r\n') for line in content]
1298              
1299          self._parse(content)
1300          # if we had any errors, now is the time to raise them
1301          if self._errors:
1302              info = "at line %s." % self._errors[0].line_number
1303              if len(self._errors) > 1:
1304                  msg = "Parsing failed with several errors.\nFirst error %s" % info
1305                  error = ConfigObjError(msg)
1306              else:
1307                  error = self._errors[0]
1308              # set the errors attribute; it's a list of tuples:
1309              # (error_type, message, line_number)
1310              error.errors = self._errors
1311              # set the config attribute
1312              error.config = self
1313              raise error
1314          # delete private attributes
1315          del self._errors
1316          
1317          if configspec is None:
1318              self.configspec = None
1319          else:
1320              self._handle_configspec(configspec)
1321      
1322      
1323      def _initialise(self, options=None):
1324          if options is None:
1325              options = OPTION_DEFAULTS
1326              
1327          # initialise a few variables
1328          self.filename = None
1329          self._errors = []
1330          self.raise_errors = options['raise_errors']
1331          self.interpolation = options['interpolation']
1332          self.list_values = options['list_values']
1333          self.create_empty = options['create_empty']
1334          self.file_error = options['file_error']
1335          self.stringify = options['stringify']
1336          self.indent_type = options['indent_type']
1337          self.encoding = options['encoding']
1338          self.default_encoding = options['default_encoding']
1339          self.BOM = False
1340          self.newlines = None
1341          self.write_empty_values = options['write_empty_values']
1342          self.unrepr = options['unrepr']
1343          
1344          self.initial_comment = []
1345          self.final_comment = []
1346          self.configspec = None
1347          
1348          if self._inspec:
1349              self.list_values = False
1350          
1351          # Clear section attributes as well
1352          Section._initialise(self)
1353          
1354          
1355      def __repr__(self):
1356          def _getval(key):
1357              try:
1358                  return self[key]
1359              except MissingInterpolationOption:
1360                  return dict.__getitem__(self, key)
1361          return ('ConfigObj({%s})' % 
1362                  ', '.join([('%s: %s' % (repr(key), repr(_getval(key)))) 
1363                  for key in (self.scalars + self.sections)]))
1364      
1365      
1366      def _handle_bom(self, infile):
1367          """
1368          Handle any BOM, and decode if necessary.
1369          
1370          If an encoding is specified, that *must* be used - but the BOM should
1371          still be removed (and the BOM attribute set).
1372          
1373          (If the encoding is wrongly specified, then a BOM for an alternative
1374          encoding won't be discovered or removed.)
1375          
1376          If an encoding is not specified, UTF8 or UTF16 BOM will be detected and
1377          removed. The BOM attribute will be set. UTF16 will be decoded to
1378          unicode.
1379          
1380          NOTE: This method must not be called with an empty ``infile``.
1381          
1382          Specifying the *wrong* encoding is likely to cause a
1383          ``UnicodeDecodeError``.
1384          
1385          ``infile`` must always be returned as a list of lines, but may be
1386          passed in as a single string.
1387          """
1388  
1389          if ((self.encoding is not None) and
1390              (self.encoding.lower() not in BOM_LIST)):
1391              # No need to check for a BOM
1392              # the encoding specified doesn't have one
1393              # just decode
1394              return self._decode(infile, self.encoding)
1395          
1396          if isinstance(infile, (list, tuple)):
1397              line = infile[0]
1398          else:
1399              line = infile
1400  
1401          if isinstance(line, str):
1402              # it's already decoded and there's no need to do anything
1403              # else, just use the _decode utility method to handle
1404              # listifying appropriately
1405              return self._decode(infile, self.encoding)
1406  
1407          if self.encoding is not None:
1408              # encoding explicitly supplied
1409              # And it could have an associated BOM
1410              # TODO: if encoding is just UTF16 - we ought to check for both
1411              # TODO: big endian and little endian versions.
1412              enc = BOM_LIST[self.encoding.lower()]
1413              if enc == 'utf_16':
1414                  # For UTF16 we try big endian and little endian
1415                  for BOM, (encoding, final_encoding) in list(BOMS.items()):
1416                      if not final_encoding:
1417                          # skip UTF8
1418                          continue
1419                      if infile.startswith(BOM):
1420                          ### BOM discovered
1421                          ##self.BOM = True
1422                          # Don't need to remove BOM
1423                          return self._decode(infile, encoding)
1424                      
1425                  # If we get this far, will *probably* raise a DecodeError
1426                  # As it doesn't appear to start with a BOM
1427                  return self._decode(infile, self.encoding)
1428              
1429              # Must be UTF8
1430              BOM = BOM_SET[enc]
1431              if not line.startswith(BOM):
1432                  return self._decode(infile, self.encoding)
1433              
1434              newline = line[len(BOM):]
1435              
1436              # BOM removed
1437              if isinstance(infile, (list, tuple)):
1438                  infile[0] = newline
1439              else:
1440                  infile = newline
1441              self.BOM = True
1442              return self._decode(infile, self.encoding)
1443          
1444          # No encoding specified - so we need to check for UTF8/UTF16
1445          for BOM, (encoding, final_encoding) in list(BOMS.items()):
1446              if not isinstance(line, bytes) or not line.startswith(BOM):
1447                  # didn't specify a BOM, or it's not a bytestring
1448                  continue
1449              else:
1450                  # BOM discovered
1451                  self.encoding = final_encoding
1452                  if not final_encoding:
1453                      self.BOM = True
1454                      # UTF8
1455                      # remove BOM
1456                      newline = line[len(BOM):]
1457                      if isinstance(infile, (list, tuple)):
1458                          infile[0] = newline
1459                      else:
1460                          infile = newline
1461                      # UTF-8
1462                      if isinstance(infile, str):
1463                          return infile.splitlines(True)
1464                      elif isinstance(infile, bytes):
1465                          return infile.decode('utf-8').splitlines(True)
1466                      else:
1467                          return self._decode(infile, 'utf-8')
1468                  # UTF16 - have to decode
1469                  return self._decode(infile, encoding)
1470              
1471  
1472          # No BOM discovered and no encoding specified, default to UTF-8
1473          if isinstance(infile, bytes):
1474              return infile.decode('utf-8').splitlines(True)
1475          else:
1476              return self._decode(infile, 'utf-8')
1477  
1478  
1479      def _a_to_u(self, aString):
1480          """Decode ASCII strings to unicode if a self.encoding is specified."""
1481          if isinstance(aString, bytes) and self.encoding:
1482              return aString.decode(self.encoding)
1483          else:
1484              return aString
1485  
1486  
1487      def _decode(self, infile, encoding):
1488          """
1489          Decode infile to unicode. Using the specified encoding.
1490          
1491          if is a string, it also needs converting to a list.
1492          """
1493          if isinstance(infile, str):
1494              return infile.splitlines(True)
1495          if isinstance(infile, bytes):
1496              # NOTE: Could raise a ``UnicodeDecodeError``
1497              if encoding:
1498                  return infile.decode(encoding).splitlines(True)
1499              else:
1500                  return infile.splitlines(True)
1501  
1502          if encoding:
1503              for i, line in enumerate(infile):
1504                  if isinstance(line, bytes):
1505                      # NOTE: The isinstance test here handles mixed lists of unicode/string
1506                      # NOTE: But the decode will break on any non-string values
1507                      # NOTE: Or could raise a ``UnicodeDecodeError``
1508                      infile[i] = line.decode(encoding)
1509          return infile
1510  
1511  
1512      def _decode_element(self, line):
1513          """Decode element to unicode if necessary."""
1514          if isinstance(line, bytes) and self.default_encoding:
1515              return line.decode(self.default_encoding)
1516          else:
1517              return line
1518  
1519  
1520      # TODO: this may need to be modified
1521      def _str(self, value):
1522          """
1523          Used by ``stringify`` within validate, to turn non-string values
1524          into strings.
1525          """
1526          if not isinstance(value, str):
1527              # intentially 'str' because it's just whatever the "normal"
1528              # string type is for the python version we're dealing with
1529              return str(value)
1530          else:
1531              return value
1532  
1533  
1534      def _parse(self, infile):
1535          """Actually parse the config file."""
1536          temp_list_values = self.list_values
1537          if self.unrepr:
1538              self.list_values = False
1539              
1540          comment_list = []
1541          done_start = False
1542          this_section = self
1543          maxline = len(infile) - 1
1544          cur_index = -1
1545          reset_comment = False
1546          
1547          while cur_index < maxline:
1548              if reset_comment:
1549                  comment_list = []
1550              cur_index += 1
1551              line = infile[cur_index]
1552              sline = line.strip()
1553              # do we have anything on the line ?
1554              if not sline or sline.startswith('#'):
1555                  reset_comment = False
1556                  comment_list.append(line)
1557                  continue
1558              
1559              if not done_start:
1560                  # preserve initial comment
1561                  self.initial_comment = comment_list
1562                  comment_list = []
1563                  done_start = True
1564                  
1565              reset_comment = True
1566              # first we check if it's a section marker
1567              mat = self._sectionmarker.match(line)
1568              if mat is not None:
1569                  # is a section line
1570                  (indent, sect_open, sect_name, sect_close, comment) = mat.groups()
1571                  if indent and (self.indent_type is None):
1572                      self.indent_type = indent
1573                  cur_depth = sect_open.count('[')
1574                  if cur_depth != sect_close.count(']'):
1575                      self._handle_error("Cannot compute the section depth",
1576                                         NestingError, infile, cur_index)
1577                      continue
1578                  
1579                  if cur_depth < this_section.depth:
1580                      # the new section is dropping back to a previous level
1581                      try:
1582                          parent = self._match_depth(this_section,
1583                                                     cur_depth).parent
1584                      except SyntaxError:
1585                          self._handle_error("Cannot compute nesting level",
1586                                             NestingError, infile, cur_index)
1587                          continue
1588                  elif cur_depth == this_section.depth:
1589                      # the new section is a sibling of the current section
1590                      parent = this_section.parent
1591                  elif cur_depth == this_section.depth + 1:
1592                      # the new section is a child the current section
1593                      parent = this_section
1594                  else:
1595                      self._handle_error("Section too nested",
1596                                         NestingError, infile, cur_index)
1597                      continue
1598                      
1599                  sect_name = self._unquote(sect_name)
1600                  if sect_name in parent:
1601                      self._handle_error('Duplicate section name',
1602                                         DuplicateError, infile, cur_index)
1603                      continue
1604                  
1605                  # create the new section
1606                  this_section = Section(
1607                      parent,
1608                      cur_depth,
1609                      self,
1610                      name=sect_name)
1611                  parent[sect_name] = this_section
1612                  parent.inline_comments[sect_name] = comment
1613                  parent.comments[sect_name] = comment_list
1614                  continue
1615              #
1616              # it's not a section marker,
1617              # so it should be a valid ``key = value`` line
1618              mat = self._keyword.match(line)
1619              if mat is None:
1620                  self._handle_error(
1621                      'Invalid line ({0!r}) (matched as neither section nor keyword)'.format(line),
1622                      ParseError, infile, cur_index)
1623              else:
1624                  # is a keyword value
1625                  # value will include any inline comment
1626                  (indent, key, value) = mat.groups()
1627                  if indent and (self.indent_type is None):
1628                      self.indent_type = indent
1629                  # check for a multiline value
1630                  if value[:3] in ['"""', "'''"]:
1631                      try:
1632                          value, comment, cur_index = self._multiline(
1633                              value, infile, cur_index, maxline)
1634                      except SyntaxError:
1635                          self._handle_error(
1636                              'Parse error in multiline value',
1637                              ParseError, infile, cur_index)
1638                          continue
1639                      else:
1640                          if self.unrepr:
1641                              comment = ''
1642                              try:
1643                                  value = unrepr(value)
1644                              except Exception as e:
1645                                  if type(e) == UnknownType:
1646                                      msg = 'Unknown name or type in value'
1647                                  else:
1648                                      msg = 'Parse error from unrepr-ing multiline value'
1649                                  self._handle_error(msg, UnreprError, infile,
1650                                      cur_index)
1651                                  continue
1652                  else:
1653                      if self.unrepr:
1654                          comment = ''
1655                          try:
1656                              value = unrepr(value)
1657                          except Exception as e:
1658                              if isinstance(e, UnknownType):
1659                                  msg = 'Unknown name or type in value'
1660                              else:
1661                                  msg = 'Parse error from unrepr-ing value'
1662                              self._handle_error(msg, UnreprError, infile,
1663                                  cur_index)
1664                              continue
1665                      else:
1666                          # extract comment and lists
1667                          try:
1668                              (value, comment) = self._handle_value(value)
1669                          except SyntaxError:
1670                              self._handle_error(
1671                                  'Parse error in value',
1672                                  ParseError, infile, cur_index)
1673                              continue
1674                  #
1675                  key = self._unquote(key)
1676                  if key in this_section:
1677                      self._handle_error(
1678                          'Duplicate keyword name',
1679                          DuplicateError, infile, cur_index)
1680                      continue
1681                  # add the key.
1682                  # we set unrepr because if we have got this far we will never
1683                  # be creating a new section
1684                  this_section.__setitem__(key, value, unrepr=True)
1685                  this_section.inline_comments[key] = comment
1686                  this_section.comments[key] = comment_list
1687                  continue
1688          #
1689          if self.indent_type is None:
1690              # no indentation used, set the type accordingly
1691              self.indent_type = ''
1692  
1693          # preserve the final comment
1694          if not self and not self.initial_comment:
1695              self.initial_comment = comment_list
1696          elif not reset_comment:
1697              self.final_comment = comment_list
1698          self.list_values = temp_list_values
1699  
1700  
1701      def _match_depth(self, sect, depth):
1702          """
1703          Given a section and a depth level, walk back through the sections
1704          parents to see if the depth level matches a previous section.
1705          
1706          Return a reference to the right section,
1707          or raise a SyntaxError.
1708          """
1709          while depth < sect.depth:
1710              if sect is sect.parent:
1711                  # we've reached the top level already
1712                  raise SyntaxError()
1713              sect = sect.parent
1714          if sect.depth == depth:
1715              return sect
1716          # shouldn't get here
1717          raise SyntaxError()
1718  
1719  
1720      def _handle_error(self, text, ErrorClass, infile, cur_index):
1721          """
1722          Handle an error according to the error settings.
1723          
1724          Either raise the error or store it.
1725          The error will have occured at ``cur_index``
1726          """
1727          line = infile[cur_index]
1728          cur_index += 1
1729          message = '{0} at line {1}.'.format(text, cur_index)
1730          error = ErrorClass(message, cur_index, line)
1731          if self.raise_errors:
1732              # raise the error - parsing stops here
1733              raise error
1734          # store the error
1735          # reraise when parsing has finished
1736          self._errors.append(error)
1737  
1738  
1739      def _unquote(self, value):
1740          """Return an unquoted version of a value"""
1741          if not value:
1742              # should only happen during parsing of lists
1743              raise SyntaxError
1744          if (value[0] == value[-1]) and (value[0] in ('"', "'")):
1745              value = value[1:-1]
1746          return value
1747  
1748  
1749      def _quote(self, value, multiline=True):
1750          """
1751          Return a safely quoted version of a value.
1752          
1753          Raise a ConfigObjError if the value cannot be safely quoted.
1754          If multiline is ``True`` (default) then use triple quotes
1755          if necessary.
1756          
1757          * Don't quote values that don't need it.
1758          * Recursively quote members of a list and return a comma joined list.
1759          * Multiline is ``False`` for lists.
1760          * Obey list syntax for empty and single member lists.
1761          
1762          If ``list_values=False`` then the value is only quoted if it contains
1763          a ``\\n`` (is multiline) or '#'.
1764          
1765          If ``write_empty_values`` is set, and the value is an empty string, it
1766          won't be quoted.
1767          """
1768          if multiline and self.write_empty_values and value == '':
1769              # Only if multiline is set, so that it is used for values not
1770              # keys, and not values that are part of a list
1771              return ''
1772          
1773          if multiline and isinstance(value, (list, tuple)):
1774              if not value:
1775                  return ','
1776              elif len(value) == 1:
1777                  return self._quote(value[0], multiline=False) + ','
1778              return ', '.join([self._quote(val, multiline=False)
1779                  for val in value])
1780          if not isinstance(value, str):
1781              if self.stringify:
1782                  # intentially 'str' because it's just whatever the "normal"
1783                  # string type is for the python version we're dealing with
1784                  value = str(value)
1785              else:
1786                  raise TypeError('Value "%s" is not a string.' % value)
1787  
1788          if not value:
1789              return '""'
1790          
1791          no_lists_no_quotes = not self.list_values and '\n' not in value and '#' not in value
1792          need_triple = multiline and ((("'" in value) and ('"' in value)) or ('\n' in value ))
1793          hash_triple_quote = multiline and not need_triple and ("'" in value) and ('"' in value) and ('#' in value)
1794          check_for_single = (no_lists_no_quotes or not need_triple) and not hash_triple_quote
1795          
1796          if check_for_single:
1797              if not self.list_values:
1798                  # we don't quote if ``list_values=False``
1799                  quot = noquot
1800              # for normal values either single or double quotes will do
1801              elif '\n' in value:
1802                  # will only happen if multiline is off - e.g. '\n' in key
1803                  raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1804              elif ((value[0] not in wspace_plus) and
1805                      (value[-1] not in wspace_plus) and
1806                      (',' not in value)):
1807                  quot = noquot
1808              else:
1809                  quot = self._get_single_quote(value)
1810          else:
1811              # if value has '\n' or "'" *and* '"', it will need triple quotes
1812              quot = self._get_triple_quote(value)
1813          
1814          if quot == noquot and '#' in value and self.list_values:
1815              quot = self._get_single_quote(value)
1816                  
1817          return quot % value
1818      
1819      
1820      def _get_single_quote(self, value):
1821          if ("'" in value) and ('"' in value):
1822              raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1823          elif '"' in value:
1824              quot = squot
1825          else:
1826              quot = dquot
1827          return quot
1828      
1829      
1830      def _get_triple_quote(self, value):
1831          if (value.find('"""') != -1) and (value.find("'''") != -1):
1832              raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1833          if value.find('"""') == -1:
1834              quot = tdquot
1835          else:
1836              quot = tsquot 
1837          return quot
1838  
1839  
1840      def _handle_value(self, value):
1841          """
1842          Given a value string, unquote, remove comment,
1843          handle lists. (including empty and single member lists)
1844          """
1845          if self._inspec:
1846              # Parsing a configspec so don't handle comments
1847              return (value, '')
1848          # do we look for lists in values ?
1849          if not self.list_values:
1850              mat = self._nolistvalue.match(value)
1851              if mat is None:
1852                  raise SyntaxError()
1853              # NOTE: we don't unquote here
1854              return mat.groups()
1855          #
1856          mat = self._valueexp.match(value)
1857          if mat is None:
1858              # the value is badly constructed, probably badly quoted,
1859              # or an invalid list
1860              raise SyntaxError()
1861          (list_values, single, empty_list, comment) = mat.groups()
1862          if (list_values == '') and (single is None):
1863              # change this if you want to accept empty values
1864              raise SyntaxError()
1865          # NOTE: note there is no error handling from here if the regex
1866          # is wrong: then incorrect values will slip through
1867          if empty_list is not None:
1868              # the single comma - meaning an empty list
1869              return ([], comment)
1870          if single is not None:
1871              # handle empty values
1872              if list_values and not single:
1873                  # FIXME: the '' is a workaround because our regex now matches
1874                  #   '' at the end of a list if it has a trailing comma
1875                  single = None
1876              else:
1877                  single = single or '""'
1878                  single = self._unquote(single)
1879          if list_values == '':
1880              # not a list value
1881              return (single, comment)
1882          the_list = self._listvalueexp.findall(list_values)
1883          the_list = [self._unquote(val) for val in the_list]
1884          if single is not None:
1885              the_list += [single]
1886          return (the_list, comment)
1887  
1888  
1889      def _multiline(self, value, infile, cur_index, maxline):
1890          """Extract the value, where we are in a multiline situation."""
1891          quot = value[:3]
1892          newvalue = value[3:]
1893          single_line = self._triple_quote[quot][0]
1894          multi_line = self._triple_quote[quot][1]
1895          mat = single_line.match(value)
1896          if mat is not None:
1897              retval = list(mat.groups())
1898              retval.append(cur_index)
1899              return retval
1900          elif newvalue.find(quot) != -1:
1901              # somehow the triple quote is missing
1902              raise SyntaxError()
1903          #
1904          while cur_index < maxline:
1905              cur_index += 1
1906              newvalue += '\n'
1907              line = infile[cur_index]
1908              if line.find(quot) == -1:
1909                  newvalue += line
1910              else:
1911                  # end of multiline, process it
1912                  break
1913          else:
1914              # we've got to the end of the config, oops...
1915              raise SyntaxError()
1916          mat = multi_line.match(line)
1917          if mat is None:
1918              # a badly formed line
1919              raise SyntaxError()
1920          (value, comment) = mat.groups()
1921          return (newvalue + value, comment, cur_index)
1922  
1923  
1924      def _handle_configspec(self, configspec):
1925          """Parse the configspec."""
1926          # FIXME: Should we check that the configspec was created with the 
1927          #        correct settings ? (i.e. ``list_values=False``)
1928          if not isinstance(configspec, ConfigObj):
1929              try:
1930                  configspec = ConfigObj(configspec,
1931                                         raise_errors=True,
1932                                         file_error=True,
1933                                         _inspec=True)
1934              except ConfigObjError as e:
1935                  # FIXME: Should these errors have a reference
1936                  #        to the already parsed ConfigObj ?
1937                  raise ConfigspecError('Parsing configspec failed: %s' % e)
1938              except IOError as e:
1939                  raise IOError('Reading configspec failed: %s' % e)
1940          
1941          self.configspec = configspec
1942              
1943  
1944          
1945      def _set_configspec(self, section, copy):
1946          """
1947          Called by validate. Handles setting the configspec on subsections
1948          including sections to be validated by __many__
1949          """
1950          configspec = section.configspec
1951          many = configspec.get('__many__')
1952          if isinstance(many, dict):
1953              for entry in section.sections:
1954                  if entry not in configspec:
1955                      section[entry].configspec = many
1956                      
1957          for entry in configspec.sections:
1958              if entry == '__many__':
1959                  continue
1960              if entry not in section:
1961                  section[entry] = {}
1962                  section[entry]._created = True
1963                  if copy:
1964                      # copy comments
1965                      section.comments[entry] = configspec.comments.get(entry, [])
1966                      section.inline_comments[entry] = configspec.inline_comments.get(entry, '')
1967                  
1968              # Could be a scalar when we expect a section
1969              if isinstance(section[entry], Section):
1970                  section[entry].configspec = configspec[entry]
1971                          
1972  
1973      def _write_line(self, indent_string, entry, this_entry, comment):
1974          """Write an individual line, for the write method"""
1975          # NOTE: the calls to self._quote here handles non-StringType values.
1976          if not self.unrepr:
1977              val = self._decode_element(self._quote(this_entry))
1978          else:
1979              val = repr(this_entry)
1980          return '%s%s%s%s%s' % (indent_string,
1981                                 self._decode_element(self._quote(entry, multiline=False)),
1982                                 self._a_to_u(' = '),
1983                                 val,
1984                                 self._decode_element(comment))
1985  
1986  
1987      def _write_marker(self, indent_string, depth, entry, comment):
1988          """Write a section marker line"""
1989          return '%s%s%s%s%s' % (indent_string,
1990                                 self._a_to_u('[' * depth),
1991                                 self._quote(self._decode_element(entry), multiline=False),
1992                                 self._a_to_u(']' * depth),
1993                                 self._decode_element(comment))
1994  
1995  
1996      def _handle_comment(self, comment):
1997          """Deal with a comment."""
1998          if not comment:
1999              return ''
2000          start = self.indent_type
2001          if not comment.startswith('#'):
2002              start += self._a_to_u(' # ')
2003          return (start + comment)
2004  
2005  
2006      # Public methods
2007  
2008      def write(self, outfile=None, section=None):
2009          """
2010          Write the current ConfigObj as a file
2011          
2012          tekNico: FIXME: use StringIO instead of real files
2013          
2014          >>> filename = a.filename
2015          >>> a.filename = 'test.ini'
2016          >>> a.write()
2017          >>> a.filename = filename
2018          >>> a == ConfigObj('test.ini', raise_errors=True)
2019          1
2020          >>> import os
2021          >>> os.remove('test.ini')
2022          """
2023          if self.indent_type is None:
2024              # this can be true if initialised from a dictionary
2025              self.indent_type = DEFAULT_INDENT_TYPE
2026              
2027          out = []
2028          cs = self._a_to_u('#')
2029          csp = self._a_to_u('# ')
2030          if section is None:
2031              int_val = self.interpolation
2032              self.interpolation = False
2033              section = self
2034              for line in self.initial_comment:
2035                  line = self._decode_element(line)
2036                  stripped_line = line.strip()
2037                  if stripped_line and not stripped_line.startswith(cs):
2038                      line = csp + line
2039                  out.append(line)
2040                  
2041          indent_string = self.indent_type * section.depth
2042          for entry in (section.scalars + section.sections):
2043              if entry in section.defaults:
2044                  # don't write out default values
2045                  continue
2046              for comment_line in section.comments[entry]:
2047                  comment_line = self._decode_element(comment_line.lstrip())
2048                  if comment_line and not comment_line.startswith(cs):
2049                      comment_line = csp + comment_line
2050                  out.append(indent_string + comment_line)
2051              this_entry = section[entry]
2052              comment = self._handle_comment(section.inline_comments[entry])
2053              
2054              if isinstance(this_entry, Section):
2055                  # a section
2056                  out.append(self._write_marker(
2057                      indent_string,
2058                      this_entry.depth,
2059                      entry,
2060                      comment))
2061                  out.extend(self.write(section=this_entry))
2062              else:
2063                  out.append(self._write_line(
2064                      indent_string,
2065                      entry,
2066                      this_entry,
2067                      comment))
2068                  
2069          if section is self:
2070              for line in self.final_comment:
2071                  line = self._decode_element(line)
2072                  stripped_line = line.strip()
2073                  if stripped_line and not stripped_line.startswith(cs):
2074                      line = csp + line
2075                  out.append(line)
2076              self.interpolation = int_val
2077              
2078          if section is not self:
2079              return out
2080          
2081          if (self.filename is None) and (outfile is None):
2082              # output a list of lines
2083              # might need to encode
2084              # NOTE: This will *screw* UTF16, each line will start with the BOM
2085              if self.encoding:
2086                  out = [l.encode(self.encoding) for l in out]
2087              if (self.BOM and ((self.encoding is None) or
2088                  (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
2089                  # Add the UTF8 BOM
2090                  if not out:
2091                      out.append('')
2092                  out[0] = BOM_UTF8 + out[0]
2093              return out
2094          
2095          # Turn the list to a string, joined with correct newlines
2096          newline = self.newlines or os.linesep
2097          if (getattr(outfile, 'mode', None) is not None and outfile.mode == 'w'
2098              and sys.platform == 'win32' and newline == '\r\n'):
2099              # Windows specific hack to avoid writing '\r\r\n'
2100              newline = '\n'
2101          output = self._a_to_u(newline).join(out)
2102          if not output.endswith(newline):
2103              output += newline
2104  
2105          if isinstance(output, bytes):
2106              output_bytes = output
2107          else:
2108              output_bytes = output.encode(self.encoding or
2109                                           self.default_encoding or
2110                                           'ascii')
2111  
2112          if self.BOM and ((self.encoding is None) or match_utf8(self.encoding)):
2113              # Add the UTF8 BOM
2114              output_bytes = BOM_UTF8 + output_bytes
2115  
2116          if outfile is not None:
2117              outfile.write(output_bytes)
2118          else:
2119              with open(self.filename, 'wb') as h:
2120                  h.write(output_bytes)
2121  
2122      def validate(self, validator, preserve_errors=False, copy=False,
2123                   section=None):
2124          """
2125          Test the ConfigObj against a configspec.
2126          
2127          It uses the ``validator`` object from *validate.py*.
2128          
2129          To run ``validate`` on the current ConfigObj, call: ::
2130          
2131              test = config.validate(validator)
2132          
2133          (Normally having previously passed in the configspec when the ConfigObj
2134          was created - you can dynamically assign a dictionary of checks to the
2135          ``configspec`` attribute of a section though).
2136          
2137          It returns ``True`` if everything passes, or a dictionary of
2138          pass/fails (True/False). If every member of a subsection passes, it
2139          will just have the value ``True``. (It also returns ``False`` if all
2140          members fail).
2141          
2142          In addition, it converts the values from strings to their native
2143          types if their checks pass (and ``stringify`` is set).
2144          
2145          If ``preserve_errors`` is ``True`` (``False`` is default) then instead
2146          of a marking a fail with a ``False``, it will preserve the actual
2147          exception object. This can contain info about the reason for failure.
2148          For example the ``VdtValueTooSmallError`` indicates that the value
2149          supplied was too small. If a value (or section) is missing it will
2150          still be marked as ``False``.
2151          
2152          You must have the validate module to use ``preserve_errors=True``.
2153          
2154          You can then use the ``flatten_errors`` function to turn your nested
2155          results dictionary into a flattened list of failures - useful for
2156          displaying meaningful error messages.
2157          """
2158          if section is None:
2159              if self.configspec is None:
2160                  raise ValueError('No configspec supplied.')
2161              if preserve_errors:
2162                  # We do this once to remove a top level dependency on the validate module
2163                  # Which makes importing configobj faster
2164                  from configobj.validate import VdtMissingValue
2165                  self._vdtMissingValue = VdtMissingValue
2166                  
2167              section = self
2168  
2169              if copy:
2170                  section.initial_comment = section.configspec.initial_comment
2171                  section.final_comment = section.configspec.final_comment
2172                  section.encoding = section.configspec.encoding
2173                  section.BOM = section.configspec.BOM
2174                  section.newlines = section.configspec.newlines
2175                  section.indent_type = section.configspec.indent_type
2176              
2177          #
2178          # section.default_values.clear() #??
2179          configspec = section.configspec
2180          self._set_configspec(section, copy)
2181  
2182          
2183          def validate_entry(entry, spec, val, missing, ret_true, ret_false):
2184              section.default_values.pop(entry, None)
2185                  
2186              try:
2187                  section.default_values[entry] = validator.get_default_value(configspec[entry])
2188              except (KeyError, AttributeError, validator.baseErrorClass):
2189                  # No default, bad default or validator has no 'get_default_value'
2190                  # (e.g. SimpleVal)
2191                  pass
2192              
2193              try:
2194                  check = validator.check(spec,
2195                                          val,
2196                                          missing=missing
2197                                          )
2198              except validator.baseErrorClass as e:
2199                  if not preserve_errors or isinstance(e, self._vdtMissingValue):
2200                      out[entry] = False
2201                  else:
2202                      # preserve the error
2203                      out[entry] = e
2204                      ret_false = False
2205                  ret_true = False
2206              else:
2207                  ret_false = False
2208                  out[entry] = True
2209                  if self.stringify or missing:
2210                      # if we are doing type conversion
2211                      # or the value is a supplied default
2212                      if not self.stringify:
2213                          if isinstance(check, (list, tuple)):
2214                              # preserve lists
2215                              check = [self._str(item) for item in check]
2216                          elif missing and check is None:
2217                              # convert the None from a default to a ''
2218                              check = ''
2219                          else:
2220                              check = self._str(check)
2221                      if (check != val) or missing:
2222                          section[entry] = check
2223                  if not copy and missing and entry not in section.defaults:
2224                      section.defaults.append(entry)
2225              return ret_true, ret_false
2226          
2227          #
2228          out = {}
2229          ret_true = True
2230          ret_false = True
2231          
2232          unvalidated = [k for k in section.scalars if k not in configspec]
2233          incorrect_sections = [k for k in configspec.sections if k in section.scalars]        
2234          incorrect_scalars = [k for k in configspec.scalars if k in section.sections]
2235          
2236          for entry in configspec.scalars:
2237              if entry in ('__many__', '___many___'):
2238                  # reserved names
2239                  continue
2240              if (not entry in section.scalars) or (entry in section.defaults):
2241                  # missing entries
2242                  # or entries from defaults
2243                  missing = True
2244                  val = None
2245                  if copy and entry not in section.scalars:
2246                      # copy comments
2247                      section.comments[entry] = (
2248                          configspec.comments.get(entry, []))
2249                      section.inline_comments[entry] = (
2250                          configspec.inline_comments.get(entry, ''))
2251                  #
2252              else:
2253                  missing = False
2254                  val = section[entry]
2255              
2256              ret_true, ret_false = validate_entry(entry, configspec[entry], val, 
2257                                                   missing, ret_true, ret_false)
2258          
2259          many = None
2260          if '__many__' in configspec.scalars:
2261              many = configspec['__many__']
2262          elif '___many___' in configspec.scalars:
2263              many = configspec['___many___']
2264          
2265          if many is not None:
2266              for entry in unvalidated:
2267                  val = section[entry]
2268                  ret_true, ret_false = validate_entry(entry, many, val, False,
2269                                                       ret_true, ret_false)
2270              unvalidated = []
2271  
2272          for entry in incorrect_scalars:
2273              ret_true = False
2274              if not preserve_errors:
2275                  out[entry] = False
2276              else:
2277                  ret_false = False
2278                  msg = 'Value %r was provided as a section' % entry
2279                  out[entry] = validator.baseErrorClass(msg)
2280          for entry in incorrect_sections:
2281              ret_true = False
2282              if not preserve_errors:
2283                  out[entry] = False
2284              else:
2285                  ret_false = False
2286                  msg = 'Section %r was provided as a single value' % entry
2287                  out[entry] = validator.baseErrorClass(msg)
2288                  
2289          # Missing sections will have been created as empty ones when the
2290          # configspec was read.
2291          for entry in section.sections:
2292              # FIXME: this means DEFAULT is not copied in copy mode
2293              if section is self and entry == 'DEFAULT':
2294                  continue
2295              if section[entry].configspec is None:
2296                  unvalidated.append(entry)
2297                  continue
2298              if copy:
2299                  section.comments[entry] = configspec.comments.get(entry, [])
2300                  section.inline_comments[entry] = configspec.inline_comments.get(entry, '')
2301              check = self.validate(validator, preserve_errors=preserve_errors, copy=copy, section=section[entry])
2302              out[entry] = check
2303              if check == False:
2304                  ret_true = False
2305              elif check == True:
2306                  ret_false = False
2307              else:
2308                  ret_true = False
2309          
2310          section.extra_values = unvalidated
2311          if preserve_errors and not section._created:
2312              # If the section wasn't created (i.e. it wasn't missing)
2313              # then we can't return False, we need to preserve errors
2314              ret_false = False
2315          #
2316          if ret_false and preserve_errors and out:
2317              # If we are preserving errors, but all
2318              # the failures are from missing sections / values
2319              # then we can return False. Otherwise there is a
2320              # real failure that we need to preserve.
2321              ret_false = not any(out.values())
2322          if ret_true:
2323              return True
2324          elif ret_false:
2325              return False
2326          return out
2327  
2328  
2329      def reset(self):
2330          """Clear ConfigObj instance and restore to 'freshly created' state."""
2331          self.clear()
2332          self._initialise()
2333          # FIXME: Should be done by '_initialise', but ConfigObj constructor (and reload)
2334          #        requires an empty dictionary
2335          self.configspec = None
2336          # Just to be sure ;-)
2337          self._original_configspec = None
2338          
2339          
2340      def reload(self):
2341          """
2342          Reload a ConfigObj from file.
2343          
2344          This method raises a ``ReloadError`` if the ConfigObj doesn't have
2345          a filename attribute pointing to a file.
2346          """
2347          if not isinstance(self.filename, str):
2348              raise ReloadError()
2349  
2350          filename = self.filename
2351          current_options = {}
2352          for entry in OPTION_DEFAULTS:
2353              if entry == 'configspec':
2354                  continue
2355              current_options[entry] = getattr(self, entry)
2356              
2357          configspec = self._original_configspec
2358          current_options['configspec'] = configspec
2359              
2360          self.clear()
2361          self._initialise(current_options)
2362          self._load(filename, configspec)
2363          
2364  
2365  
2366  class SimpleVal(object):
2367      """
2368      A simple validator.
2369      Can be used to check that all members expected are present.
2370      
2371      To use it, provide a configspec with all your members in (the value given
2372      will be ignored). Pass an instance of ``SimpleVal`` to the ``validate``
2373      method of your ``ConfigObj``. ``validate`` will return ``True`` if all
2374      members are present, or a dictionary with True/False meaning
2375      present/missing. (Whole missing sections will be replaced with ``False``)
2376      """
2377      
2378      def __init__(self):
2379          self.baseErrorClass = ConfigObjError
2380      
2381      def check(self, check, member, missing=False):
2382          """A dummy check method, always returns the value unchanged."""
2383          if missing:
2384              raise self.baseErrorClass()
2385          return member
2386  
2387  
2388  def flatten_errors(cfg, res, levels=None, results=None):
2389      """
2390      An example function that will turn a nested dictionary of results
2391      (as returned by ``ConfigObj.validate``) into a flat list.
2392      
2393      ``cfg`` is the ConfigObj instance being checked, ``res`` is the results
2394      dictionary returned by ``validate``.
2395      
2396      (This is a recursive function, so you shouldn't use the ``levels`` or
2397      ``results`` arguments - they are used by the function.)
2398      
2399      Returns a list of keys that failed. Each member of the list is a tuple::
2400      
2401          ([list of sections...], key, result)
2402      
2403      If ``validate`` was called with ``preserve_errors=False`` (the default)
2404      then ``result`` will always be ``False``.
2405  
2406      *list of sections* is a flattened list of sections that the key was found
2407      in.
2408      
2409      If the section was missing (or a section was expected and a scalar provided
2410      - or vice-versa) then key will be ``None``.
2411      
2412      If the value (or section) was missing then ``result`` will be ``False``.
2413      
2414      If ``validate`` was called with ``preserve_errors=True`` and a value
2415      was present, but failed the check, then ``result`` will be the exception
2416      object returned. You can use this as a string that describes the failure.
2417      
2418      For example *The value "3" is of the wrong type*.
2419      """
2420      if levels is None:
2421          # first time called
2422          levels = []
2423          results = []
2424      if res == True:
2425          return sorted(results)
2426      if res == False or isinstance(res, Exception):
2427          results.append((levels[:], None, res))
2428          if levels:
2429              levels.pop()
2430          return sorted(results)
2431      for (key, val) in list(res.items()):
2432          if val == True:
2433              continue
2434          if isinstance(cfg.get(key), dict):
2435              # Go down one level
2436              levels.append(key)
2437              flatten_errors(cfg[key], val, levels, results)
2438              continue
2439          results.append((levels[:], key, val))
2440      #
2441      # Go up one level
2442      if levels:
2443          levels.pop()
2444      #
2445      return sorted(results)
2446  
2447  
2448  def get_extra_values(conf, _prepend=()):
2449      """
2450      Find all the values and sections not in the configspec from a validated
2451      ConfigObj.
2452      
2453      ``get_extra_values`` returns a list of tuples where each tuple represents
2454      either an extra section, or an extra value.
2455      
2456      The tuples contain two values, a tuple representing the section the value 
2457      is in and the name of the extra values. For extra values in the top level
2458      section the first member will be an empty tuple. For values in the 'foo'
2459      section the first member will be ``('foo',)``. For members in the 'bar'
2460      subsection of the 'foo' section the first member will be ``('foo', 'bar')``.
2461      
2462      NOTE: If you call ``get_extra_values`` on a ConfigObj instance that hasn't
2463      been validated it will return an empty list.
2464      """
2465      out = []
2466      
2467      out.extend([(_prepend, name) for name in conf.extra_values])
2468      for name in conf.sections:
2469          if name not in conf.extra_values:
2470              out.extend(get_extra_values(conf[name], _prepend + (name,)))
2471      return out
2472  
2473  
2474  """*A programming language is a medium of expression.* - Paul Graham"""