config/configobj.py

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

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

mercurial