Package pythonutils :: Module configobj
[hide private]
[frames] | no frames]

Source Code for Module pythonutils.configobj

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