python/configobj/configobj.py

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial