1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/config/configobj.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2279 @@ 1.4 +# configobj.py 1.5 +# A config file reader/writer that supports nested sections in config files. 1.6 +# Copyright (C) 2005-2006 Michael Foord, Nicola Larosa 1.7 +# E-mail: fuzzyman AT voidspace DOT org DOT uk 1.8 +# nico AT tekNico DOT net 1.9 + 1.10 +# ConfigObj 4 1.11 +# http://www.voidspace.org.uk/python/configobj.html 1.12 + 1.13 +# Released subject to the BSD License 1.14 +# Please see http://www.voidspace.org.uk/python/license.shtml 1.15 + 1.16 +# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml 1.17 +# For information about bugfixes, updates and support, please join the 1.18 +# ConfigObj mailing list: 1.19 +# http://lists.sourceforge.net/lists/listinfo/configobj-develop 1.20 +# Comments, suggestions and bug reports welcome. 1.21 + 1.22 +from __future__ import generators 1.23 + 1.24 +import sys 1.25 +INTP_VER = sys.version_info[:2] 1.26 +if INTP_VER < (2, 2): 1.27 + raise RuntimeError("Python v.2.2 or later needed") 1.28 + 1.29 +import os, re 1.30 +compiler = None 1.31 +try: 1.32 + import compiler 1.33 +except ImportError: 1.34 + # for IronPython 1.35 + pass 1.36 +from types import StringTypes 1.37 +from warnings import warn 1.38 +try: 1.39 + from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE 1.40 +except ImportError: 1.41 + # Python 2.2 does not have these 1.42 + # UTF-8 1.43 + BOM_UTF8 = '\xef\xbb\xbf' 1.44 + # UTF-16, little endian 1.45 + BOM_UTF16_LE = '\xff\xfe' 1.46 + # UTF-16, big endian 1.47 + BOM_UTF16_BE = '\xfe\xff' 1.48 + if sys.byteorder == 'little': 1.49 + # UTF-16, native endianness 1.50 + BOM_UTF16 = BOM_UTF16_LE 1.51 + else: 1.52 + # UTF-16, native endianness 1.53 + BOM_UTF16 = BOM_UTF16_BE 1.54 + 1.55 +# A dictionary mapping BOM to 1.56 +# the encoding to decode with, and what to set the 1.57 +# encoding attribute to. 1.58 +BOMS = { 1.59 + BOM_UTF8: ('utf_8', None), 1.60 + BOM_UTF16_BE: ('utf16_be', 'utf_16'), 1.61 + BOM_UTF16_LE: ('utf16_le', 'utf_16'), 1.62 + BOM_UTF16: ('utf_16', 'utf_16'), 1.63 + } 1.64 +# All legal variants of the BOM codecs. 1.65 +# TODO: the list of aliases is not meant to be exhaustive, is there a 1.66 +# better way ? 1.67 +BOM_LIST = { 1.68 + 'utf_16': 'utf_16', 1.69 + 'u16': 'utf_16', 1.70 + 'utf16': 'utf_16', 1.71 + 'utf-16': 'utf_16', 1.72 + 'utf16_be': 'utf16_be', 1.73 + 'utf_16_be': 'utf16_be', 1.74 + 'utf-16be': 'utf16_be', 1.75 + 'utf16_le': 'utf16_le', 1.76 + 'utf_16_le': 'utf16_le', 1.77 + 'utf-16le': 'utf16_le', 1.78 + 'utf_8': 'utf_8', 1.79 + 'u8': 'utf_8', 1.80 + 'utf': 'utf_8', 1.81 + 'utf8': 'utf_8', 1.82 + 'utf-8': 'utf_8', 1.83 + } 1.84 + 1.85 +# Map of encodings to the BOM to write. 1.86 +BOM_SET = { 1.87 + 'utf_8': BOM_UTF8, 1.88 + 'utf_16': BOM_UTF16, 1.89 + 'utf16_be': BOM_UTF16_BE, 1.90 + 'utf16_le': BOM_UTF16_LE, 1.91 + None: BOM_UTF8 1.92 + } 1.93 + 1.94 +try: 1.95 + from validate import VdtMissingValue 1.96 +except ImportError: 1.97 + VdtMissingValue = None 1.98 + 1.99 +try: 1.100 + enumerate 1.101 +except NameError: 1.102 + def enumerate(obj): 1.103 + """enumerate for Python 2.2.""" 1.104 + i = -1 1.105 + for item in obj: 1.106 + i += 1 1.107 + yield i, item 1.108 + 1.109 +try: 1.110 + True, False 1.111 +except NameError: 1.112 + True, False = 1, 0 1.113 + 1.114 + 1.115 +__version__ = '4.4.0' 1.116 + 1.117 +__revision__ = '$Id: configobj.py,v 3.5 2007/07/02 18:20:24 benjamin%smedbergs.us Exp $' 1.118 + 1.119 +__docformat__ = "restructuredtext en" 1.120 + 1.121 +__all__ = ( 1.122 + '__version__', 1.123 + 'DEFAULT_INDENT_TYPE', 1.124 + 'DEFAULT_INTERPOLATION', 1.125 + 'ConfigObjError', 1.126 + 'NestingError', 1.127 + 'ParseError', 1.128 + 'DuplicateError', 1.129 + 'ConfigspecError', 1.130 + 'ConfigObj', 1.131 + 'SimpleVal', 1.132 + 'InterpolationError', 1.133 + 'InterpolationLoopError', 1.134 + 'MissingInterpolationOption', 1.135 + 'RepeatSectionError', 1.136 + 'UnreprError', 1.137 + 'UnknownType', 1.138 + '__docformat__', 1.139 + 'flatten_errors', 1.140 +) 1.141 + 1.142 +DEFAULT_INTERPOLATION = 'configparser' 1.143 +DEFAULT_INDENT_TYPE = ' ' 1.144 +MAX_INTERPOL_DEPTH = 10 1.145 + 1.146 +OPTION_DEFAULTS = { 1.147 + 'interpolation': True, 1.148 + 'raise_errors': False, 1.149 + 'list_values': True, 1.150 + 'create_empty': False, 1.151 + 'file_error': False, 1.152 + 'configspec': None, 1.153 + 'stringify': True, 1.154 + # option may be set to one of ('', ' ', '\t') 1.155 + 'indent_type': None, 1.156 + 'encoding': None, 1.157 + 'default_encoding': None, 1.158 + 'unrepr': False, 1.159 + 'write_empty_values': False, 1.160 +} 1.161 + 1.162 + 1.163 +def getObj(s): 1.164 + s = "a=" + s 1.165 + if compiler is None: 1.166 + raise ImportError('compiler module not available') 1.167 + p = compiler.parse(s) 1.168 + return p.getChildren()[1].getChildren()[0].getChildren()[1] 1.169 + 1.170 +class UnknownType(Exception): 1.171 + pass 1.172 + 1.173 +class Builder: 1.174 + 1.175 + def build(self, o): 1.176 + m = getattr(self, 'build_' + o.__class__.__name__, None) 1.177 + if m is None: 1.178 + raise UnknownType(o.__class__.__name__) 1.179 + return m(o) 1.180 + 1.181 + def build_List(self, o): 1.182 + return map(self.build, o.getChildren()) 1.183 + 1.184 + def build_Const(self, o): 1.185 + return o.value 1.186 + 1.187 + def build_Dict(self, o): 1.188 + d = {} 1.189 + i = iter(map(self.build, o.getChildren())) 1.190 + for el in i: 1.191 + d[el] = i.next() 1.192 + return d 1.193 + 1.194 + def build_Tuple(self, o): 1.195 + return tuple(self.build_List(o)) 1.196 + 1.197 + def build_Name(self, o): 1.198 + if o.name == 'None': 1.199 + return None 1.200 + if o.name == 'True': 1.201 + return True 1.202 + if o.name == 'False': 1.203 + return False 1.204 + 1.205 + # An undefinted Name 1.206 + raise UnknownType('Undefined Name') 1.207 + 1.208 + def build_Add(self, o): 1.209 + real, imag = map(self.build_Const, o.getChildren()) 1.210 + try: 1.211 + real = float(real) 1.212 + except TypeError: 1.213 + raise UnknownType('Add') 1.214 + if not isinstance(imag, complex) or imag.real != 0.0: 1.215 + raise UnknownType('Add') 1.216 + return real+imag 1.217 + 1.218 + def build_Getattr(self, o): 1.219 + parent = self.build(o.expr) 1.220 + return getattr(parent, o.attrname) 1.221 + 1.222 + def build_UnarySub(self, o): 1.223 + return -self.build_Const(o.getChildren()[0]) 1.224 + 1.225 + def build_UnaryAdd(self, o): 1.226 + return self.build_Const(o.getChildren()[0]) 1.227 + 1.228 +def unrepr(s): 1.229 + if not s: 1.230 + return s 1.231 + return Builder().build(getObj(s)) 1.232 + 1.233 +def _splitlines(instring): 1.234 + """Split a string on lines, without losing line endings or truncating.""" 1.235 + 1.236 + 1.237 +class ConfigObjError(SyntaxError): 1.238 + """ 1.239 + This is the base class for all errors that ConfigObj raises. 1.240 + It is a subclass of SyntaxError. 1.241 + """ 1.242 + def __init__(self, message='', line_number=None, line=''): 1.243 + self.line = line 1.244 + self.line_number = line_number 1.245 + self.message = message 1.246 + SyntaxError.__init__(self, message) 1.247 + 1.248 +class NestingError(ConfigObjError): 1.249 + """ 1.250 + This error indicates a level of nesting that doesn't match. 1.251 + """ 1.252 + 1.253 +class ParseError(ConfigObjError): 1.254 + """ 1.255 + This error indicates that a line is badly written. 1.256 + It is neither a valid ``key = value`` line, 1.257 + nor a valid section marker line. 1.258 + """ 1.259 + 1.260 +class DuplicateError(ConfigObjError): 1.261 + """ 1.262 + The keyword or section specified already exists. 1.263 + """ 1.264 + 1.265 +class ConfigspecError(ConfigObjError): 1.266 + """ 1.267 + An error occurred whilst parsing a configspec. 1.268 + """ 1.269 + 1.270 +class InterpolationError(ConfigObjError): 1.271 + """Base class for the two interpolation errors.""" 1.272 + 1.273 +class InterpolationLoopError(InterpolationError): 1.274 + """Maximum interpolation depth exceeded in string interpolation.""" 1.275 + 1.276 + def __init__(self, option): 1.277 + InterpolationError.__init__( 1.278 + self, 1.279 + 'interpolation loop detected in value "%s".' % option) 1.280 + 1.281 +class RepeatSectionError(ConfigObjError): 1.282 + """ 1.283 + This error indicates additional sections in a section with a 1.284 + ``__many__`` (repeated) section. 1.285 + """ 1.286 + 1.287 +class MissingInterpolationOption(InterpolationError): 1.288 + """A value specified for interpolation was missing.""" 1.289 + 1.290 + def __init__(self, option): 1.291 + InterpolationError.__init__( 1.292 + self, 1.293 + 'missing option "%s" in interpolation.' % option) 1.294 + 1.295 +class UnreprError(ConfigObjError): 1.296 + """An error parsing in unrepr mode.""" 1.297 + 1.298 + 1.299 +class InterpolationEngine(object): 1.300 + """ 1.301 + A helper class to help perform string interpolation. 1.302 + 1.303 + This class is an abstract base class; its descendants perform 1.304 + the actual work. 1.305 + """ 1.306 + 1.307 + # compiled regexp to use in self.interpolate() 1.308 + _KEYCRE = re.compile(r"%\(([^)]*)\)s") 1.309 + 1.310 + def __init__(self, section): 1.311 + # the Section instance that "owns" this engine 1.312 + self.section = section 1.313 + 1.314 + def interpolate(self, key, value): 1.315 + def recursive_interpolate(key, value, section, backtrail): 1.316 + """The function that does the actual work. 1.317 + 1.318 + ``value``: the string we're trying to interpolate. 1.319 + ``section``: the section in which that string was found 1.320 + ``backtrail``: a dict to keep track of where we've been, 1.321 + to detect and prevent infinite recursion loops 1.322 + 1.323 + This is similar to a depth-first-search algorithm. 1.324 + """ 1.325 + # Have we been here already? 1.326 + if backtrail.has_key((key, section.name)): 1.327 + # Yes - infinite loop detected 1.328 + raise InterpolationLoopError(key) 1.329 + # Place a marker on our backtrail so we won't come back here again 1.330 + backtrail[(key, section.name)] = 1 1.331 + 1.332 + # Now start the actual work 1.333 + match = self._KEYCRE.search(value) 1.334 + while match: 1.335 + # The actual parsing of the match is implementation-dependent, 1.336 + # so delegate to our helper function 1.337 + k, v, s = self._parse_match(match) 1.338 + if k is None: 1.339 + # That's the signal that no further interpolation is needed 1.340 + replacement = v 1.341 + else: 1.342 + # Further interpolation may be needed to obtain final value 1.343 + replacement = recursive_interpolate(k, v, s, backtrail) 1.344 + # Replace the matched string with its final value 1.345 + start, end = match.span() 1.346 + value = ''.join((value[:start], replacement, value[end:])) 1.347 + new_search_start = start + len(replacement) 1.348 + # Pick up the next interpolation key, if any, for next time 1.349 + # through the while loop 1.350 + match = self._KEYCRE.search(value, new_search_start) 1.351 + 1.352 + # Now safe to come back here again; remove marker from backtrail 1.353 + del backtrail[(key, section.name)] 1.354 + 1.355 + return value 1.356 + 1.357 + # Back in interpolate(), all we have to do is kick off the recursive 1.358 + # function with appropriate starting values 1.359 + value = recursive_interpolate(key, value, self.section, {}) 1.360 + return value 1.361 + 1.362 + def _fetch(self, key): 1.363 + """Helper function to fetch values from owning section. 1.364 + 1.365 + Returns a 2-tuple: the value, and the section where it was found. 1.366 + """ 1.367 + # switch off interpolation before we try and fetch anything ! 1.368 + save_interp = self.section.main.interpolation 1.369 + self.section.main.interpolation = False 1.370 + 1.371 + # Start at section that "owns" this InterpolationEngine 1.372 + current_section = self.section 1.373 + while True: 1.374 + # try the current section first 1.375 + val = current_section.get(key) 1.376 + if val is not None: 1.377 + break 1.378 + # try "DEFAULT" next 1.379 + val = current_section.get('DEFAULT', {}).get(key) 1.380 + if val is not None: 1.381 + break 1.382 + # move up to parent and try again 1.383 + # top-level's parent is itself 1.384 + if current_section.parent is current_section: 1.385 + # reached top level, time to give up 1.386 + break 1.387 + current_section = current_section.parent 1.388 + 1.389 + # restore interpolation to previous value before returning 1.390 + self.section.main.interpolation = save_interp 1.391 + if val is None: 1.392 + raise MissingInterpolationOption(key) 1.393 + return val, current_section 1.394 + 1.395 + def _parse_match(self, match): 1.396 + """Implementation-dependent helper function. 1.397 + 1.398 + Will be passed a match object corresponding to the interpolation 1.399 + key we just found (e.g., "%(foo)s" or "$foo"). Should look up that 1.400 + key in the appropriate config file section (using the ``_fetch()`` 1.401 + helper function) and return a 3-tuple: (key, value, section) 1.402 + 1.403 + ``key`` is the name of the key we're looking for 1.404 + ``value`` is the value found for that key 1.405 + ``section`` is a reference to the section where it was found 1.406 + 1.407 + ``key`` and ``section`` should be None if no further 1.408 + interpolation should be performed on the resulting value 1.409 + (e.g., if we interpolated "$$" and returned "$"). 1.410 + """ 1.411 + raise NotImplementedError 1.412 + 1.413 + 1.414 +class ConfigParserInterpolation(InterpolationEngine): 1.415 + """Behaves like ConfigParser.""" 1.416 + _KEYCRE = re.compile(r"%\(([^)]*)\)s") 1.417 + 1.418 + def _parse_match(self, match): 1.419 + key = match.group(1) 1.420 + value, section = self._fetch(key) 1.421 + return key, value, section 1.422 + 1.423 + 1.424 +class TemplateInterpolation(InterpolationEngine): 1.425 + """Behaves like string.Template.""" 1.426 + _delimiter = '$' 1.427 + _KEYCRE = re.compile(r""" 1.428 + \$(?: 1.429 + (?P<escaped>\$) | # Two $ signs 1.430 + (?P<named>[_a-z][_a-z0-9]*) | # $name format 1.431 + {(?P<braced>[^}]*)} # ${name} format 1.432 + ) 1.433 + """, re.IGNORECASE | re.VERBOSE) 1.434 + 1.435 + def _parse_match(self, match): 1.436 + # Valid name (in or out of braces): fetch value from section 1.437 + key = match.group('named') or match.group('braced') 1.438 + if key is not None: 1.439 + value, section = self._fetch(key) 1.440 + return key, value, section 1.441 + # Escaped delimiter (e.g., $$): return single delimiter 1.442 + if match.group('escaped') is not None: 1.443 + # Return None for key and section to indicate it's time to stop 1.444 + return None, self._delimiter, None 1.445 + # Anything else: ignore completely, just return it unchanged 1.446 + return None, match.group(), None 1.447 + 1.448 +interpolation_engines = { 1.449 + 'configparser': ConfigParserInterpolation, 1.450 + 'template': TemplateInterpolation, 1.451 +} 1.452 + 1.453 +class Section(dict): 1.454 + """ 1.455 + A dictionary-like object that represents a section in a config file. 1.456 + 1.457 + It does string interpolation if the 'interpolation' attribute 1.458 + of the 'main' object is set to True. 1.459 + 1.460 + Interpolation is tried first from this object, then from the 'DEFAULT' 1.461 + section of this object, next from the parent and its 'DEFAULT' section, 1.462 + and so on until the main object is reached. 1.463 + 1.464 + A Section will behave like an ordered dictionary - following the 1.465 + order of the ``scalars`` and ``sections`` attributes. 1.466 + You can use this to change the order of members. 1.467 + 1.468 + Iteration follows the order: scalars, then sections. 1.469 + """ 1.470 + 1.471 + def __init__(self, parent, depth, main, indict=None, name=None): 1.472 + """ 1.473 + * parent is the section above 1.474 + * depth is the depth level of this section 1.475 + * main is the main ConfigObj 1.476 + * indict is a dictionary to initialise the section with 1.477 + """ 1.478 + if indict is None: 1.479 + indict = {} 1.480 + dict.__init__(self) 1.481 + # used for nesting level *and* interpolation 1.482 + self.parent = parent 1.483 + # used for the interpolation attribute 1.484 + self.main = main 1.485 + # level of nesting depth of this Section 1.486 + self.depth = depth 1.487 + # the sequence of scalar values in this Section 1.488 + self.scalars = [] 1.489 + # the sequence of sections in this Section 1.490 + self.sections = [] 1.491 + # purely for information 1.492 + self.name = name 1.493 + # for comments :-) 1.494 + self.comments = {} 1.495 + self.inline_comments = {} 1.496 + # for the configspec 1.497 + self.configspec = {} 1.498 + self._order = [] 1.499 + self._configspec_comments = {} 1.500 + self._configspec_inline_comments = {} 1.501 + self._cs_section_comments = {} 1.502 + self._cs_section_inline_comments = {} 1.503 + # for defaults 1.504 + self.defaults = [] 1.505 + # 1.506 + # we do this explicitly so that __setitem__ is used properly 1.507 + # (rather than just passing to ``dict.__init__``) 1.508 + for entry in indict: 1.509 + self[entry] = indict[entry] 1.510 + 1.511 + def _interpolate(self, key, value): 1.512 + try: 1.513 + # do we already have an interpolation engine? 1.514 + engine = self._interpolation_engine 1.515 + except AttributeError: 1.516 + # not yet: first time running _interpolate(), so pick the engine 1.517 + name = self.main.interpolation 1.518 + if name == True: # note that "if name:" would be incorrect here 1.519 + # backwards-compatibility: interpolation=True means use default 1.520 + name = DEFAULT_INTERPOLATION 1.521 + name = name.lower() # so that "Template", "template", etc. all work 1.522 + class_ = interpolation_engines.get(name, None) 1.523 + if class_ is None: 1.524 + # invalid value for self.main.interpolation 1.525 + self.main.interpolation = False 1.526 + return value 1.527 + else: 1.528 + # save reference to engine so we don't have to do this again 1.529 + engine = self._interpolation_engine = class_(self) 1.530 + # let the engine do the actual work 1.531 + return engine.interpolate(key, value) 1.532 + 1.533 + def __getitem__(self, key): 1.534 + """Fetch the item and do string interpolation.""" 1.535 + val = dict.__getitem__(self, key) 1.536 + if self.main.interpolation and isinstance(val, StringTypes): 1.537 + return self._interpolate(key, val) 1.538 + return val 1.539 + 1.540 + def __setitem__(self, key, value, unrepr=False): 1.541 + """ 1.542 + Correctly set a value. 1.543 + 1.544 + Making dictionary values Section instances. 1.545 + (We have to special case 'Section' instances - which are also dicts) 1.546 + 1.547 + Keys must be strings. 1.548 + Values need only be strings (or lists of strings) if 1.549 + ``main.stringify`` is set. 1.550 + 1.551 + `unrepr`` must be set when setting a value to a dictionary, without 1.552 + creating a new sub-section. 1.553 + """ 1.554 + if not isinstance(key, StringTypes): 1.555 + raise ValueError, 'The key "%s" is not a string.' % key 1.556 + # add the comment 1.557 + if not self.comments.has_key(key): 1.558 + self.comments[key] = [] 1.559 + self.inline_comments[key] = '' 1.560 + # remove the entry from defaults 1.561 + if key in self.defaults: 1.562 + self.defaults.remove(key) 1.563 + # 1.564 + if isinstance(value, Section): 1.565 + if not self.has_key(key): 1.566 + self.sections.append(key) 1.567 + dict.__setitem__(self, key, value) 1.568 + elif isinstance(value, dict) and not unrepr: 1.569 + # First create the new depth level, 1.570 + # then create the section 1.571 + if not self.has_key(key): 1.572 + self.sections.append(key) 1.573 + new_depth = self.depth + 1 1.574 + dict.__setitem__( 1.575 + self, 1.576 + key, 1.577 + Section( 1.578 + self, 1.579 + new_depth, 1.580 + self.main, 1.581 + indict=value, 1.582 + name=key)) 1.583 + else: 1.584 + if not self.has_key(key): 1.585 + self.scalars.append(key) 1.586 + if not self.main.stringify: 1.587 + if isinstance(value, StringTypes): 1.588 + pass 1.589 + elif isinstance(value, (list, tuple)): 1.590 + for entry in value: 1.591 + if not isinstance(entry, StringTypes): 1.592 + raise TypeError, ( 1.593 + 'Value is not a string "%s".' % entry) 1.594 + else: 1.595 + raise TypeError, 'Value is not a string "%s".' % value 1.596 + dict.__setitem__(self, key, value) 1.597 + 1.598 + def __delitem__(self, key): 1.599 + """Remove items from the sequence when deleting.""" 1.600 + dict. __delitem__(self, key) 1.601 + if key in self.scalars: 1.602 + self.scalars.remove(key) 1.603 + else: 1.604 + self.sections.remove(key) 1.605 + del self.comments[key] 1.606 + del self.inline_comments[key] 1.607 + 1.608 + def get(self, key, default=None): 1.609 + """A version of ``get`` that doesn't bypass string interpolation.""" 1.610 + try: 1.611 + return self[key] 1.612 + except KeyError: 1.613 + return default 1.614 + 1.615 + def update(self, indict): 1.616 + """ 1.617 + A version of update that uses our ``__setitem__``. 1.618 + """ 1.619 + for entry in indict: 1.620 + self[entry] = indict[entry] 1.621 + 1.622 + def pop(self, key, *args): 1.623 + """ """ 1.624 + val = dict.pop(self, key, *args) 1.625 + if key in self.scalars: 1.626 + del self.comments[key] 1.627 + del self.inline_comments[key] 1.628 + self.scalars.remove(key) 1.629 + elif key in self.sections: 1.630 + del self.comments[key] 1.631 + del self.inline_comments[key] 1.632 + self.sections.remove(key) 1.633 + if self.main.interpolation and isinstance(val, StringTypes): 1.634 + return self._interpolate(key, val) 1.635 + return val 1.636 + 1.637 + def popitem(self): 1.638 + """Pops the first (key,val)""" 1.639 + sequence = (self.scalars + self.sections) 1.640 + if not sequence: 1.641 + raise KeyError, ": 'popitem(): dictionary is empty'" 1.642 + key = sequence[0] 1.643 + val = self[key] 1.644 + del self[key] 1.645 + return key, val 1.646 + 1.647 + def clear(self): 1.648 + """ 1.649 + A version of clear that also affects scalars/sections 1.650 + Also clears comments and configspec. 1.651 + 1.652 + Leaves other attributes alone : 1.653 + depth/main/parent are not affected 1.654 + """ 1.655 + dict.clear(self) 1.656 + self.scalars = [] 1.657 + self.sections = [] 1.658 + self.comments = {} 1.659 + self.inline_comments = {} 1.660 + self.configspec = {} 1.661 + 1.662 + def setdefault(self, key, default=None): 1.663 + """A version of setdefault that sets sequence if appropriate.""" 1.664 + try: 1.665 + return self[key] 1.666 + except KeyError: 1.667 + self[key] = default 1.668 + return self[key] 1.669 + 1.670 + def items(self): 1.671 + """ """ 1.672 + return zip((self.scalars + self.sections), self.values()) 1.673 + 1.674 + def keys(self): 1.675 + """ """ 1.676 + return (self.scalars + self.sections) 1.677 + 1.678 + def values(self): 1.679 + """ """ 1.680 + return [self[key] for key in (self.scalars + self.sections)] 1.681 + 1.682 + def iteritems(self): 1.683 + """ """ 1.684 + return iter(self.items()) 1.685 + 1.686 + def iterkeys(self): 1.687 + """ """ 1.688 + return iter((self.scalars + self.sections)) 1.689 + 1.690 + __iter__ = iterkeys 1.691 + 1.692 + def itervalues(self): 1.693 + """ """ 1.694 + return iter(self.values()) 1.695 + 1.696 + def __repr__(self): 1.697 + return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key]))) 1.698 + for key in (self.scalars + self.sections)]) 1.699 + 1.700 + __str__ = __repr__ 1.701 + 1.702 + # Extra methods - not in a normal dictionary 1.703 + 1.704 + def dict(self): 1.705 + """ 1.706 + Return a deepcopy of self as a dictionary. 1.707 + 1.708 + All members that are ``Section`` instances are recursively turned to 1.709 + ordinary dictionaries - by calling their ``dict`` method. 1.710 + 1.711 + >>> n = a.dict() 1.712 + >>> n == a 1.713 + 1 1.714 + >>> n is a 1.715 + 0 1.716 + """ 1.717 + newdict = {} 1.718 + for entry in self: 1.719 + this_entry = self[entry] 1.720 + if isinstance(this_entry, Section): 1.721 + this_entry = this_entry.dict() 1.722 + elif isinstance(this_entry, list): 1.723 + # create a copy rather than a reference 1.724 + this_entry = list(this_entry) 1.725 + elif isinstance(this_entry, tuple): 1.726 + # create a copy rather than a reference 1.727 + this_entry = tuple(this_entry) 1.728 + newdict[entry] = this_entry 1.729 + return newdict 1.730 + 1.731 + def merge(self, indict): 1.732 + """ 1.733 + A recursive update - useful for merging config files. 1.734 + 1.735 + >>> a = '''[section1] 1.736 + ... option1 = True 1.737 + ... [[subsection]] 1.738 + ... more_options = False 1.739 + ... # end of file'''.splitlines() 1.740 + >>> b = '''# File is user.ini 1.741 + ... [section1] 1.742 + ... option1 = False 1.743 + ... # end of file'''.splitlines() 1.744 + >>> c1 = ConfigObj(b) 1.745 + >>> c2 = ConfigObj(a) 1.746 + >>> c2.merge(c1) 1.747 + >>> c2 1.748 + {'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}} 1.749 + """ 1.750 + for key, val in indict.items(): 1.751 + if (key in self and isinstance(self[key], dict) and 1.752 + isinstance(val, dict)): 1.753 + self[key].merge(val) 1.754 + else: 1.755 + self[key] = val 1.756 + 1.757 + def rename(self, oldkey, newkey): 1.758 + """ 1.759 + Change a keyname to another, without changing position in sequence. 1.760 + 1.761 + Implemented so that transformations can be made on keys, 1.762 + as well as on values. (used by encode and decode) 1.763 + 1.764 + Also renames comments. 1.765 + """ 1.766 + if oldkey in self.scalars: 1.767 + the_list = self.scalars 1.768 + elif oldkey in self.sections: 1.769 + the_list = self.sections 1.770 + else: 1.771 + raise KeyError, 'Key "%s" not found.' % oldkey 1.772 + pos = the_list.index(oldkey) 1.773 + # 1.774 + val = self[oldkey] 1.775 + dict.__delitem__(self, oldkey) 1.776 + dict.__setitem__(self, newkey, val) 1.777 + the_list.remove(oldkey) 1.778 + the_list.insert(pos, newkey) 1.779 + comm = self.comments[oldkey] 1.780 + inline_comment = self.inline_comments[oldkey] 1.781 + del self.comments[oldkey] 1.782 + del self.inline_comments[oldkey] 1.783 + self.comments[newkey] = comm 1.784 + self.inline_comments[newkey] = inline_comment 1.785 + 1.786 + def walk(self, function, raise_errors=True, 1.787 + call_on_sections=False, **keywargs): 1.788 + """ 1.789 + Walk every member and call a function on the keyword and value. 1.790 + 1.791 + Return a dictionary of the return values 1.792 + 1.793 + If the function raises an exception, raise the errror 1.794 + unless ``raise_errors=False``, in which case set the return value to 1.795 + ``False``. 1.796 + 1.797 + Any unrecognised keyword arguments you pass to walk, will be pased on 1.798 + to the function you pass in. 1.799 + 1.800 + Note: if ``call_on_sections`` is ``True`` then - on encountering a 1.801 + subsection, *first* the function is called for the *whole* subsection, 1.802 + and then recurses into its members. This means your function must be 1.803 + able to handle strings, dictionaries and lists. This allows you 1.804 + to change the key of subsections as well as for ordinary members. The 1.805 + return value when called on the whole subsection has to be discarded. 1.806 + 1.807 + See the encode and decode methods for examples, including functions. 1.808 + 1.809 + .. caution:: 1.810 + 1.811 + You can use ``walk`` to transform the names of members of a section 1.812 + but you mustn't add or delete members. 1.813 + 1.814 + >>> config = '''[XXXXsection] 1.815 + ... XXXXkey = XXXXvalue'''.splitlines() 1.816 + >>> cfg = ConfigObj(config) 1.817 + >>> cfg 1.818 + {'XXXXsection': {'XXXXkey': 'XXXXvalue'}} 1.819 + >>> def transform(section, key): 1.820 + ... val = section[key] 1.821 + ... newkey = key.replace('XXXX', 'CLIENT1') 1.822 + ... section.rename(key, newkey) 1.823 + ... if isinstance(val, (tuple, list, dict)): 1.824 + ... pass 1.825 + ... else: 1.826 + ... val = val.replace('XXXX', 'CLIENT1') 1.827 + ... section[newkey] = val 1.828 + >>> cfg.walk(transform, call_on_sections=True) 1.829 + {'CLIENT1section': {'CLIENT1key': None}} 1.830 + >>> cfg 1.831 + {'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}} 1.832 + """ 1.833 + out = {} 1.834 + # scalars first 1.835 + for i in range(len(self.scalars)): 1.836 + entry = self.scalars[i] 1.837 + try: 1.838 + val = function(self, entry, **keywargs) 1.839 + # bound again in case name has changed 1.840 + entry = self.scalars[i] 1.841 + out[entry] = val 1.842 + except Exception: 1.843 + if raise_errors: 1.844 + raise 1.845 + else: 1.846 + entry = self.scalars[i] 1.847 + out[entry] = False 1.848 + # then sections 1.849 + for i in range(len(self.sections)): 1.850 + entry = self.sections[i] 1.851 + if call_on_sections: 1.852 + try: 1.853 + function(self, entry, **keywargs) 1.854 + except Exception: 1.855 + if raise_errors: 1.856 + raise 1.857 + else: 1.858 + entry = self.sections[i] 1.859 + out[entry] = False 1.860 + # bound again in case name has changed 1.861 + entry = self.sections[i] 1.862 + # previous result is discarded 1.863 + out[entry] = self[entry].walk( 1.864 + function, 1.865 + raise_errors=raise_errors, 1.866 + call_on_sections=call_on_sections, 1.867 + **keywargs) 1.868 + return out 1.869 + 1.870 + def decode(self, encoding): 1.871 + """ 1.872 + Decode all strings and values to unicode, using the specified encoding. 1.873 + 1.874 + Works with subsections and list values. 1.875 + 1.876 + Uses the ``walk`` method. 1.877 + 1.878 + Testing ``encode`` and ``decode``. 1.879 + >>> m = ConfigObj(a) 1.880 + >>> m.decode('ascii') 1.881 + >>> def testuni(val): 1.882 + ... for entry in val: 1.883 + ... if not isinstance(entry, unicode): 1.884 + ... print >> sys.stderr, type(entry) 1.885 + ... raise AssertionError, 'decode failed.' 1.886 + ... if isinstance(val[entry], dict): 1.887 + ... testuni(val[entry]) 1.888 + ... elif not isinstance(val[entry], unicode): 1.889 + ... raise AssertionError, 'decode failed.' 1.890 + >>> testuni(m) 1.891 + >>> m.encode('ascii') 1.892 + >>> a == m 1.893 + 1 1.894 + """ 1.895 + warn('use of ``decode`` is deprecated.', DeprecationWarning) 1.896 + def decode(section, key, encoding=encoding, warn=True): 1.897 + """ """ 1.898 + val = section[key] 1.899 + if isinstance(val, (list, tuple)): 1.900 + newval = [] 1.901 + for entry in val: 1.902 + newval.append(entry.decode(encoding)) 1.903 + elif isinstance(val, dict): 1.904 + newval = val 1.905 + else: 1.906 + newval = val.decode(encoding) 1.907 + newkey = key.decode(encoding) 1.908 + section.rename(key, newkey) 1.909 + section[newkey] = newval 1.910 + # using ``call_on_sections`` allows us to modify section names 1.911 + self.walk(decode, call_on_sections=True) 1.912 + 1.913 + def encode(self, encoding): 1.914 + """ 1.915 + Encode all strings and values from unicode, 1.916 + using the specified encoding. 1.917 + 1.918 + Works with subsections and list values. 1.919 + Uses the ``walk`` method. 1.920 + """ 1.921 + warn('use of ``encode`` is deprecated.', DeprecationWarning) 1.922 + def encode(section, key, encoding=encoding): 1.923 + """ """ 1.924 + val = section[key] 1.925 + if isinstance(val, (list, tuple)): 1.926 + newval = [] 1.927 + for entry in val: 1.928 + newval.append(entry.encode(encoding)) 1.929 + elif isinstance(val, dict): 1.930 + newval = val 1.931 + else: 1.932 + newval = val.encode(encoding) 1.933 + newkey = key.encode(encoding) 1.934 + section.rename(key, newkey) 1.935 + section[newkey] = newval 1.936 + self.walk(encode, call_on_sections=True) 1.937 + 1.938 + def istrue(self, key): 1.939 + """A deprecated version of ``as_bool``.""" 1.940 + warn('use of ``istrue`` is deprecated. Use ``as_bool`` method ' 1.941 + 'instead.', DeprecationWarning) 1.942 + return self.as_bool(key) 1.943 + 1.944 + def as_bool(self, key): 1.945 + """ 1.946 + Accepts a key as input. The corresponding value must be a string or 1.947 + the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to 1.948 + retain compatibility with Python 2.2. 1.949 + 1.950 + If the string is one of ``True``, ``On``, ``Yes``, or ``1`` it returns 1.951 + ``True``. 1.952 + 1.953 + If the string is one of ``False``, ``Off``, ``No``, or ``0`` it returns 1.954 + ``False``. 1.955 + 1.956 + ``as_bool`` is not case sensitive. 1.957 + 1.958 + Any other input will raise a ``ValueError``. 1.959 + 1.960 + >>> a = ConfigObj() 1.961 + >>> a['a'] = 'fish' 1.962 + >>> a.as_bool('a') 1.963 + Traceback (most recent call last): 1.964 + ValueError: Value "fish" is neither True nor False 1.965 + >>> a['b'] = 'True' 1.966 + >>> a.as_bool('b') 1.967 + 1 1.968 + >>> a['b'] = 'off' 1.969 + >>> a.as_bool('b') 1.970 + 0 1.971 + """ 1.972 + val = self[key] 1.973 + if val == True: 1.974 + return True 1.975 + elif val == False: 1.976 + return False 1.977 + else: 1.978 + try: 1.979 + if not isinstance(val, StringTypes): 1.980 + raise KeyError 1.981 + else: 1.982 + return self.main._bools[val.lower()] 1.983 + except KeyError: 1.984 + raise ValueError('Value "%s" is neither True nor False' % val) 1.985 + 1.986 + def as_int(self, key): 1.987 + """ 1.988 + A convenience method which coerces the specified value to an integer. 1.989 + 1.990 + If the value is an invalid literal for ``int``, a ``ValueError`` will 1.991 + be raised. 1.992 + 1.993 + >>> a = ConfigObj() 1.994 + >>> a['a'] = 'fish' 1.995 + >>> a.as_int('a') 1.996 + Traceback (most recent call last): 1.997 + ValueError: invalid literal for int(): fish 1.998 + >>> a['b'] = '1' 1.999 + >>> a.as_int('b') 1.1000 + 1 1.1001 + >>> a['b'] = '3.2' 1.1002 + >>> a.as_int('b') 1.1003 + Traceback (most recent call last): 1.1004 + ValueError: invalid literal for int(): 3.2 1.1005 + """ 1.1006 + return int(self[key]) 1.1007 + 1.1008 + def as_float(self, key): 1.1009 + """ 1.1010 + A convenience method which coerces the specified value to a float. 1.1011 + 1.1012 + If the value is an invalid literal for ``float``, a ``ValueError`` will 1.1013 + be raised. 1.1014 + 1.1015 + >>> a = ConfigObj() 1.1016 + >>> a['a'] = 'fish' 1.1017 + >>> a.as_float('a') 1.1018 + Traceback (most recent call last): 1.1019 + ValueError: invalid literal for float(): fish 1.1020 + >>> a['b'] = '1' 1.1021 + >>> a.as_float('b') 1.1022 + 1.0 1.1023 + >>> a['b'] = '3.2' 1.1024 + >>> a.as_float('b') 1.1025 + 3.2000000000000002 1.1026 + """ 1.1027 + return float(self[key]) 1.1028 + 1.1029 + 1.1030 +class ConfigObj(Section): 1.1031 + """An object to read, create, and write config files.""" 1.1032 + 1.1033 + _keyword = re.compile(r'''^ # line start 1.1034 + (\s*) # indentation 1.1035 + ( # keyword 1.1036 + (?:".*?")| # double quotes 1.1037 + (?:'.*?')| # single quotes 1.1038 + (?:[^'"=].*?) # no quotes 1.1039 + ) 1.1040 + \s*=\s* # divider 1.1041 + (.*) # value (including list values and comments) 1.1042 + $ # line end 1.1043 + ''', 1.1044 + re.VERBOSE) 1.1045 + 1.1046 + _sectionmarker = re.compile(r'''^ 1.1047 + (\s*) # 1: indentation 1.1048 + ((?:\[\s*)+) # 2: section marker open 1.1049 + ( # 3: section name open 1.1050 + (?:"\s*\S.*?\s*")| # at least one non-space with double quotes 1.1051 + (?:'\s*\S.*?\s*')| # at least one non-space with single quotes 1.1052 + (?:[^'"\s].*?) # at least one non-space unquoted 1.1053 + ) # section name close 1.1054 + ((?:\s*\])+) # 4: section marker close 1.1055 + \s*(\#.*)? # 5: optional comment 1.1056 + $''', 1.1057 + re.VERBOSE) 1.1058 + 1.1059 + # this regexp pulls list values out as a single string 1.1060 + # or single values and comments 1.1061 + # FIXME: this regex adds a '' to the end of comma terminated lists 1.1062 + # workaround in ``_handle_value`` 1.1063 + _valueexp = re.compile(r'''^ 1.1064 + (?: 1.1065 + (?: 1.1066 + ( 1.1067 + (?: 1.1068 + (?: 1.1069 + (?:".*?")| # double quotes 1.1070 + (?:'.*?')| # single quotes 1.1071 + (?:[^'",\#][^,\#]*?) # unquoted 1.1072 + ) 1.1073 + \s*,\s* # comma 1.1074 + )* # match all list items ending in a comma (if any) 1.1075 + ) 1.1076 + ( 1.1077 + (?:".*?")| # double quotes 1.1078 + (?:'.*?')| # single quotes 1.1079 + (?:[^'",\#\s][^,]*?)| # unquoted 1.1080 + (?:(?<!,)) # Empty value 1.1081 + )? # last item in a list - or string value 1.1082 + )| 1.1083 + (,) # alternatively a single comma - empty list 1.1084 + ) 1.1085 + \s*(\#.*)? # optional comment 1.1086 + $''', 1.1087 + re.VERBOSE) 1.1088 + 1.1089 + # use findall to get the members of a list value 1.1090 + _listvalueexp = re.compile(r''' 1.1091 + ( 1.1092 + (?:".*?")| # double quotes 1.1093 + (?:'.*?')| # single quotes 1.1094 + (?:[^'",\#].*?) # unquoted 1.1095 + ) 1.1096 + \s*,\s* # comma 1.1097 + ''', 1.1098 + re.VERBOSE) 1.1099 + 1.1100 + # this regexp is used for the value 1.1101 + # when lists are switched off 1.1102 + _nolistvalue = re.compile(r'''^ 1.1103 + ( 1.1104 + (?:".*?")| # double quotes 1.1105 + (?:'.*?')| # single quotes 1.1106 + (?:[^'"\#].*?)| # unquoted 1.1107 + (?:) # Empty value 1.1108 + ) 1.1109 + \s*(\#.*)? # optional comment 1.1110 + $''', 1.1111 + re.VERBOSE) 1.1112 + 1.1113 + # regexes for finding triple quoted values on one line 1.1114 + _single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$") 1.1115 + _single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$') 1.1116 + _multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$") 1.1117 + _multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$') 1.1118 + 1.1119 + _triple_quote = { 1.1120 + "'''": (_single_line_single, _multi_line_single), 1.1121 + '"""': (_single_line_double, _multi_line_double), 1.1122 + } 1.1123 + 1.1124 + # Used by the ``istrue`` Section method 1.1125 + _bools = { 1.1126 + 'yes': True, 'no': False, 1.1127 + 'on': True, 'off': False, 1.1128 + '1': True, '0': False, 1.1129 + 'true': True, 'false': False, 1.1130 + } 1.1131 + 1.1132 + def __init__(self, infile=None, options=None, **kwargs): 1.1133 + """ 1.1134 + Parse or create a config file object. 1.1135 + 1.1136 + ``ConfigObj(infile=None, options=None, **kwargs)`` 1.1137 + """ 1.1138 + if infile is None: 1.1139 + infile = [] 1.1140 + if options is None: 1.1141 + options = {} 1.1142 + else: 1.1143 + options = dict(options) 1.1144 + # keyword arguments take precedence over an options dictionary 1.1145 + options.update(kwargs) 1.1146 + # init the superclass 1.1147 + Section.__init__(self, self, 0, self) 1.1148 + # 1.1149 + defaults = OPTION_DEFAULTS.copy() 1.1150 + for entry in options.keys(): 1.1151 + if entry not in defaults.keys(): 1.1152 + raise TypeError, 'Unrecognised option "%s".' % entry 1.1153 + # TODO: check the values too. 1.1154 + # 1.1155 + # Add any explicit options to the defaults 1.1156 + defaults.update(options) 1.1157 + # 1.1158 + # initialise a few variables 1.1159 + self.filename = None 1.1160 + self._errors = [] 1.1161 + self.raise_errors = defaults['raise_errors'] 1.1162 + self.interpolation = defaults['interpolation'] 1.1163 + self.list_values = defaults['list_values'] 1.1164 + self.create_empty = defaults['create_empty'] 1.1165 + self.file_error = defaults['file_error'] 1.1166 + self.stringify = defaults['stringify'] 1.1167 + self.indent_type = defaults['indent_type'] 1.1168 + self.encoding = defaults['encoding'] 1.1169 + self.default_encoding = defaults['default_encoding'] 1.1170 + self.BOM = False 1.1171 + self.newlines = None 1.1172 + self.write_empty_values = defaults['write_empty_values'] 1.1173 + self.unrepr = defaults['unrepr'] 1.1174 + # 1.1175 + self.initial_comment = [] 1.1176 + self.final_comment = [] 1.1177 + # 1.1178 + self._terminated = False 1.1179 + # 1.1180 + if isinstance(infile, StringTypes): 1.1181 + self.filename = infile 1.1182 + if os.path.isfile(infile): 1.1183 + infile = open(infile).read() or [] 1.1184 + elif self.file_error: 1.1185 + # raise an error if the file doesn't exist 1.1186 + raise IOError, 'Config file not found: "%s".' % self.filename 1.1187 + else: 1.1188 + # file doesn't already exist 1.1189 + if self.create_empty: 1.1190 + # this is a good test that the filename specified 1.1191 + # isn't impossible - like on a non existent device 1.1192 + h = open(infile, 'w') 1.1193 + h.write('') 1.1194 + h.close() 1.1195 + infile = [] 1.1196 + elif isinstance(infile, (list, tuple)): 1.1197 + infile = list(infile) 1.1198 + elif isinstance(infile, dict): 1.1199 + # initialise self 1.1200 + # the Section class handles creating subsections 1.1201 + if isinstance(infile, ConfigObj): 1.1202 + # get a copy of our ConfigObj 1.1203 + infile = infile.dict() 1.1204 + for entry in infile: 1.1205 + self[entry] = infile[entry] 1.1206 + del self._errors 1.1207 + if defaults['configspec'] is not None: 1.1208 + self._handle_configspec(defaults['configspec']) 1.1209 + else: 1.1210 + self.configspec = None 1.1211 + return 1.1212 + elif hasattr(infile, 'read'): 1.1213 + # This supports file like objects 1.1214 + infile = infile.read() or [] 1.1215 + # needs splitting into lines - but needs doing *after* decoding 1.1216 + # in case it's not an 8 bit encoding 1.1217 + else: 1.1218 + raise TypeError, ('infile must be a filename,' 1.1219 + ' file like object, or list of lines.') 1.1220 + # 1.1221 + if infile: 1.1222 + # don't do it for the empty ConfigObj 1.1223 + infile = self._handle_bom(infile) 1.1224 + # infile is now *always* a list 1.1225 + # 1.1226 + # Set the newlines attribute (first line ending it finds) 1.1227 + # and strip trailing '\n' or '\r' from lines 1.1228 + for line in infile: 1.1229 + if (not line) or (line[-1] not in ('\r', '\n', '\r\n')): 1.1230 + continue 1.1231 + for end in ('\r\n', '\n', '\r'): 1.1232 + if line.endswith(end): 1.1233 + self.newlines = end 1.1234 + break 1.1235 + break 1.1236 + if infile[-1] and infile[-1] in ('\r', '\n', '\r\n'): 1.1237 + self._terminated = True 1.1238 + infile = [line.rstrip('\r\n') for line in infile] 1.1239 + # 1.1240 + self._parse(infile) 1.1241 + # if we had any errors, now is the time to raise them 1.1242 + if self._errors: 1.1243 + info = "at line %s." % self._errors[0].line_number 1.1244 + if len(self._errors) > 1: 1.1245 + msg = ("Parsing failed with several errors.\nFirst error %s" % 1.1246 + info) 1.1247 + error = ConfigObjError(msg) 1.1248 + else: 1.1249 + error = self._errors[0] 1.1250 + # set the errors attribute; it's a list of tuples: 1.1251 + # (error_type, message, line_number) 1.1252 + error.errors = self._errors 1.1253 + # set the config attribute 1.1254 + error.config = self 1.1255 + raise error 1.1256 + # delete private attributes 1.1257 + del self._errors 1.1258 + # 1.1259 + if defaults['configspec'] is None: 1.1260 + self.configspec = None 1.1261 + else: 1.1262 + self._handle_configspec(defaults['configspec']) 1.1263 + 1.1264 + def __repr__(self): 1.1265 + return 'ConfigObj({%s})' % ', '.join( 1.1266 + [('%s: %s' % (repr(key), repr(self[key]))) for key in 1.1267 + (self.scalars + self.sections)]) 1.1268 + 1.1269 + def _handle_bom(self, infile): 1.1270 + """ 1.1271 + Handle any BOM, and decode if necessary. 1.1272 + 1.1273 + If an encoding is specified, that *must* be used - but the BOM should 1.1274 + still be removed (and the BOM attribute set). 1.1275 + 1.1276 + (If the encoding is wrongly specified, then a BOM for an alternative 1.1277 + encoding won't be discovered or removed.) 1.1278 + 1.1279 + If an encoding is not specified, UTF8 or UTF16 BOM will be detected and 1.1280 + removed. The BOM attribute will be set. UTF16 will be decoded to 1.1281 + unicode. 1.1282 + 1.1283 + NOTE: This method must not be called with an empty ``infile``. 1.1284 + 1.1285 + Specifying the *wrong* encoding is likely to cause a 1.1286 + ``UnicodeDecodeError``. 1.1287 + 1.1288 + ``infile`` must always be returned as a list of lines, but may be 1.1289 + passed in as a single string. 1.1290 + """ 1.1291 + if ((self.encoding is not None) and 1.1292 + (self.encoding.lower() not in BOM_LIST)): 1.1293 + # No need to check for a BOM 1.1294 + # the encoding specified doesn't have one 1.1295 + # just decode 1.1296 + return self._decode(infile, self.encoding) 1.1297 + # 1.1298 + if isinstance(infile, (list, tuple)): 1.1299 + line = infile[0] 1.1300 + else: 1.1301 + line = infile 1.1302 + if self.encoding is not None: 1.1303 + # encoding explicitly supplied 1.1304 + # And it could have an associated BOM 1.1305 + # TODO: if encoding is just UTF16 - we ought to check for both 1.1306 + # TODO: big endian and little endian versions. 1.1307 + enc = BOM_LIST[self.encoding.lower()] 1.1308 + if enc == 'utf_16': 1.1309 + # For UTF16 we try big endian and little endian 1.1310 + for BOM, (encoding, final_encoding) in BOMS.items(): 1.1311 + if not final_encoding: 1.1312 + # skip UTF8 1.1313 + continue 1.1314 + if infile.startswith(BOM): 1.1315 + ### BOM discovered 1.1316 + ##self.BOM = True 1.1317 + # Don't need to remove BOM 1.1318 + return self._decode(infile, encoding) 1.1319 + # 1.1320 + # If we get this far, will *probably* raise a DecodeError 1.1321 + # As it doesn't appear to start with a BOM 1.1322 + return self._decode(infile, self.encoding) 1.1323 + # 1.1324 + # Must be UTF8 1.1325 + BOM = BOM_SET[enc] 1.1326 + if not line.startswith(BOM): 1.1327 + return self._decode(infile, self.encoding) 1.1328 + # 1.1329 + newline = line[len(BOM):] 1.1330 + # 1.1331 + # BOM removed 1.1332 + if isinstance(infile, (list, tuple)): 1.1333 + infile[0] = newline 1.1334 + else: 1.1335 + infile = newline 1.1336 + self.BOM = True 1.1337 + return self._decode(infile, self.encoding) 1.1338 + # 1.1339 + # No encoding specified - so we need to check for UTF8/UTF16 1.1340 + for BOM, (encoding, final_encoding) in BOMS.items(): 1.1341 + if not line.startswith(BOM): 1.1342 + continue 1.1343 + else: 1.1344 + # BOM discovered 1.1345 + self.encoding = final_encoding 1.1346 + if not final_encoding: 1.1347 + self.BOM = True 1.1348 + # UTF8 1.1349 + # remove BOM 1.1350 + newline = line[len(BOM):] 1.1351 + if isinstance(infile, (list, tuple)): 1.1352 + infile[0] = newline 1.1353 + else: 1.1354 + infile = newline 1.1355 + # UTF8 - don't decode 1.1356 + if isinstance(infile, StringTypes): 1.1357 + return infile.splitlines(True) 1.1358 + else: 1.1359 + return infile 1.1360 + # UTF16 - have to decode 1.1361 + return self._decode(infile, encoding) 1.1362 + # 1.1363 + # No BOM discovered and no encoding specified, just return 1.1364 + if isinstance(infile, StringTypes): 1.1365 + # infile read from a file will be a single string 1.1366 + return infile.splitlines(True) 1.1367 + else: 1.1368 + return infile 1.1369 + 1.1370 + def _a_to_u(self, aString): 1.1371 + """Decode ASCII strings to unicode if a self.encoding is specified.""" 1.1372 + if self.encoding: 1.1373 + return aString.decode('ascii') 1.1374 + else: 1.1375 + return aString 1.1376 + 1.1377 + def _decode(self, infile, encoding): 1.1378 + """ 1.1379 + Decode infile to unicode. Using the specified encoding. 1.1380 + 1.1381 + if is a string, it also needs converting to a list. 1.1382 + """ 1.1383 + if isinstance(infile, StringTypes): 1.1384 + # can't be unicode 1.1385 + # NOTE: Could raise a ``UnicodeDecodeError`` 1.1386 + return infile.decode(encoding).splitlines(True) 1.1387 + for i, line in enumerate(infile): 1.1388 + if not isinstance(line, unicode): 1.1389 + # NOTE: The isinstance test here handles mixed lists of unicode/string 1.1390 + # NOTE: But the decode will break on any non-string values 1.1391 + # NOTE: Or could raise a ``UnicodeDecodeError`` 1.1392 + infile[i] = line.decode(encoding) 1.1393 + return infile 1.1394 + 1.1395 + def _decode_element(self, line): 1.1396 + """Decode element to unicode if necessary.""" 1.1397 + if not self.encoding: 1.1398 + return line 1.1399 + if isinstance(line, str) and self.default_encoding: 1.1400 + return line.decode(self.default_encoding) 1.1401 + return line 1.1402 + 1.1403 + def _str(self, value): 1.1404 + """ 1.1405 + Used by ``stringify`` within validate, to turn non-string values 1.1406 + into strings. 1.1407 + """ 1.1408 + if not isinstance(value, StringTypes): 1.1409 + return str(value) 1.1410 + else: 1.1411 + return value 1.1412 + 1.1413 + def _parse(self, infile): 1.1414 + """Actually parse the config file.""" 1.1415 + temp_list_values = self.list_values 1.1416 + if self.unrepr: 1.1417 + self.list_values = False 1.1418 + comment_list = [] 1.1419 + done_start = False 1.1420 + this_section = self 1.1421 + maxline = len(infile) - 1 1.1422 + cur_index = -1 1.1423 + reset_comment = False 1.1424 + while cur_index < maxline: 1.1425 + if reset_comment: 1.1426 + comment_list = [] 1.1427 + cur_index += 1 1.1428 + line = infile[cur_index] 1.1429 + sline = line.strip() 1.1430 + # do we have anything on the line ? 1.1431 + if not sline or sline.startswith('#') or sline.startswith(';'): 1.1432 + reset_comment = False 1.1433 + comment_list.append(line) 1.1434 + continue 1.1435 + if not done_start: 1.1436 + # preserve initial comment 1.1437 + self.initial_comment = comment_list 1.1438 + comment_list = [] 1.1439 + done_start = True 1.1440 + reset_comment = True 1.1441 + # first we check if it's a section marker 1.1442 + mat = self._sectionmarker.match(line) 1.1443 + if mat is not None: 1.1444 + # is a section line 1.1445 + (indent, sect_open, sect_name, sect_close, comment) = ( 1.1446 + mat.groups()) 1.1447 + if indent and (self.indent_type is None): 1.1448 + self.indent_type = indent 1.1449 + cur_depth = sect_open.count('[') 1.1450 + if cur_depth != sect_close.count(']'): 1.1451 + self._handle_error( 1.1452 + "Cannot compute the section depth at line %s.", 1.1453 + NestingError, infile, cur_index) 1.1454 + continue 1.1455 + # 1.1456 + if cur_depth < this_section.depth: 1.1457 + # the new section is dropping back to a previous level 1.1458 + try: 1.1459 + parent = self._match_depth( 1.1460 + this_section, 1.1461 + cur_depth).parent 1.1462 + except SyntaxError: 1.1463 + self._handle_error( 1.1464 + "Cannot compute nesting level at line %s.", 1.1465 + NestingError, infile, cur_index) 1.1466 + continue 1.1467 + elif cur_depth == this_section.depth: 1.1468 + # the new section is a sibling of the current section 1.1469 + parent = this_section.parent 1.1470 + elif cur_depth == this_section.depth + 1: 1.1471 + # the new section is a child the current section 1.1472 + parent = this_section 1.1473 + else: 1.1474 + self._handle_error( 1.1475 + "Section too nested at line %s.", 1.1476 + NestingError, infile, cur_index) 1.1477 + # 1.1478 + sect_name = self._unquote(sect_name) 1.1479 + if parent.has_key(sect_name): 1.1480 + self._handle_error( 1.1481 + 'Duplicate section name at line %s.', 1.1482 + DuplicateError, infile, cur_index) 1.1483 + continue 1.1484 + # create the new section 1.1485 + this_section = Section( 1.1486 + parent, 1.1487 + cur_depth, 1.1488 + self, 1.1489 + name=sect_name) 1.1490 + parent[sect_name] = this_section 1.1491 + parent.inline_comments[sect_name] = comment 1.1492 + parent.comments[sect_name] = comment_list 1.1493 + continue 1.1494 + # 1.1495 + # it's not a section marker, 1.1496 + # so it should be a valid ``key = value`` line 1.1497 + mat = self._keyword.match(line) 1.1498 + if mat is None: 1.1499 + # it neither matched as a keyword 1.1500 + # or a section marker 1.1501 + self._handle_error( 1.1502 + 'Invalid line at line "%s".', 1.1503 + ParseError, infile, cur_index) 1.1504 + else: 1.1505 + # is a keyword value 1.1506 + # value will include any inline comment 1.1507 + (indent, key, value) = mat.groups() 1.1508 + if indent and (self.indent_type is None): 1.1509 + self.indent_type = indent 1.1510 + # check for a multiline value 1.1511 + if value[:3] in ['"""', "'''"]: 1.1512 + try: 1.1513 + (value, comment, cur_index) = self._multiline( 1.1514 + value, infile, cur_index, maxline) 1.1515 + except SyntaxError: 1.1516 + self._handle_error( 1.1517 + 'Parse error in value at line %s.', 1.1518 + ParseError, infile, cur_index) 1.1519 + continue 1.1520 + else: 1.1521 + if self.unrepr: 1.1522 + comment = '' 1.1523 + try: 1.1524 + value = unrepr(value) 1.1525 + except Exception, e: 1.1526 + if type(e) == UnknownType: 1.1527 + msg = 'Unknown name or type in value at line %s.' 1.1528 + else: 1.1529 + msg = 'Parse error in value at line %s.' 1.1530 + self._handle_error(msg, UnreprError, infile, 1.1531 + cur_index) 1.1532 + continue 1.1533 + else: 1.1534 + if self.unrepr: 1.1535 + comment = '' 1.1536 + try: 1.1537 + value = unrepr(value) 1.1538 + except Exception, e: 1.1539 + if isinstance(e, UnknownType): 1.1540 + msg = 'Unknown name or type in value at line %s.' 1.1541 + else: 1.1542 + msg = 'Parse error in value at line %s.' 1.1543 + self._handle_error(msg, UnreprError, infile, 1.1544 + cur_index) 1.1545 + continue 1.1546 + else: 1.1547 + # extract comment and lists 1.1548 + try: 1.1549 + (value, comment) = self._handle_value(value) 1.1550 + except SyntaxError: 1.1551 + self._handle_error( 1.1552 + 'Parse error in value at line %s.', 1.1553 + ParseError, infile, cur_index) 1.1554 + continue 1.1555 + # 1.1556 + key = self._unquote(key) 1.1557 + if this_section.has_key(key): 1.1558 + self._handle_error( 1.1559 + 'Duplicate keyword name at line %s.', 1.1560 + DuplicateError, infile, cur_index) 1.1561 + continue 1.1562 + # add the key. 1.1563 + # we set unrepr because if we have got this far we will never 1.1564 + # be creating a new section 1.1565 + this_section.__setitem__(key, value, unrepr=True) 1.1566 + this_section.inline_comments[key] = comment 1.1567 + this_section.comments[key] = comment_list 1.1568 + continue 1.1569 + # 1.1570 + if self.indent_type is None: 1.1571 + # no indentation used, set the type accordingly 1.1572 + self.indent_type = '' 1.1573 + # 1.1574 + if self._terminated: 1.1575 + comment_list.append('') 1.1576 + # preserve the final comment 1.1577 + if not self and not self.initial_comment: 1.1578 + self.initial_comment = comment_list 1.1579 + elif not reset_comment: 1.1580 + self.final_comment = comment_list 1.1581 + self.list_values = temp_list_values 1.1582 + 1.1583 + def _match_depth(self, sect, depth): 1.1584 + """ 1.1585 + Given a section and a depth level, walk back through the sections 1.1586 + parents to see if the depth level matches a previous section. 1.1587 + 1.1588 + Return a reference to the right section, 1.1589 + or raise a SyntaxError. 1.1590 + """ 1.1591 + while depth < sect.depth: 1.1592 + if sect is sect.parent: 1.1593 + # we've reached the top level already 1.1594 + raise SyntaxError 1.1595 + sect = sect.parent 1.1596 + if sect.depth == depth: 1.1597 + return sect 1.1598 + # shouldn't get here 1.1599 + raise SyntaxError 1.1600 + 1.1601 + def _handle_error(self, text, ErrorClass, infile, cur_index): 1.1602 + """ 1.1603 + Handle an error according to the error settings. 1.1604 + 1.1605 + Either raise the error or store it. 1.1606 + The error will have occurred at ``cur_index`` 1.1607 + """ 1.1608 + line = infile[cur_index] 1.1609 + cur_index += 1 1.1610 + message = text % cur_index 1.1611 + error = ErrorClass(message, cur_index, line) 1.1612 + if self.raise_errors: 1.1613 + # raise the error - parsing stops here 1.1614 + raise error 1.1615 + # store the error 1.1616 + # reraise when parsing has finished 1.1617 + self._errors.append(error) 1.1618 + 1.1619 + def _unquote(self, value): 1.1620 + """Return an unquoted version of a value""" 1.1621 + if (value[0] == value[-1]) and (value[0] in ('"', "'")): 1.1622 + value = value[1:-1] 1.1623 + return value 1.1624 + 1.1625 + def _quote(self, value, multiline=True): 1.1626 + """ 1.1627 + Return a safely quoted version of a value. 1.1628 + 1.1629 + Raise a ConfigObjError if the value cannot be safely quoted. 1.1630 + If multiline is ``True`` (default) then use triple quotes 1.1631 + if necessary. 1.1632 + 1.1633 + Don't quote values that don't need it. 1.1634 + Recursively quote members of a list and return a comma joined list. 1.1635 + Multiline is ``False`` for lists. 1.1636 + Obey list syntax for empty and single member lists. 1.1637 + 1.1638 + If ``list_values=False`` then the value is only quoted if it contains 1.1639 + a ``\n`` (is multiline). 1.1640 + 1.1641 + If ``write_empty_values`` is set, and the value is an empty string, it 1.1642 + won't be quoted. 1.1643 + """ 1.1644 + if multiline and self.write_empty_values and value == '': 1.1645 + # Only if multiline is set, so that it is used for values not 1.1646 + # keys, and not values that are part of a list 1.1647 + return '' 1.1648 + if multiline and isinstance(value, (list, tuple)): 1.1649 + if not value: 1.1650 + return ',' 1.1651 + elif len(value) == 1: 1.1652 + return self._quote(value[0], multiline=False) + ',' 1.1653 + return ', '.join([self._quote(val, multiline=False) 1.1654 + for val in value]) 1.1655 + if not isinstance(value, StringTypes): 1.1656 + if self.stringify: 1.1657 + value = str(value) 1.1658 + else: 1.1659 + raise TypeError, 'Value "%s" is not a string.' % value 1.1660 + squot = "'%s'" 1.1661 + dquot = '"%s"' 1.1662 + noquot = "%s" 1.1663 + wspace_plus = ' \r\t\n\v\t\'"' 1.1664 + tsquot = '"""%s"""' 1.1665 + tdquot = "'''%s'''" 1.1666 + if not value: 1.1667 + return '""' 1.1668 + if (not self.list_values and '\n' not in value) or not (multiline and 1.1669 + ((("'" in value) and ('"' in value)) or ('\n' in value))): 1.1670 + if not self.list_values: 1.1671 + # we don't quote if ``list_values=False`` 1.1672 + quot = noquot 1.1673 + # for normal values either single or double quotes will do 1.1674 + elif '\n' in value: 1.1675 + # will only happen if multiline is off - e.g. '\n' in key 1.1676 + raise ConfigObjError, ('Value "%s" cannot be safely quoted.' % 1.1677 + value) 1.1678 + elif ((value[0] not in wspace_plus) and 1.1679 + (value[-1] not in wspace_plus) and 1.1680 + (',' not in value)): 1.1681 + quot = noquot 1.1682 + else: 1.1683 + if ("'" in value) and ('"' in value): 1.1684 + raise ConfigObjError, ( 1.1685 + 'Value "%s" cannot be safely quoted.' % value) 1.1686 + elif '"' in value: 1.1687 + quot = squot 1.1688 + else: 1.1689 + quot = dquot 1.1690 + else: 1.1691 + # if value has '\n' or "'" *and* '"', it will need triple quotes 1.1692 + if (value.find('"""') != -1) and (value.find("'''") != -1): 1.1693 + raise ConfigObjError, ( 1.1694 + 'Value "%s" cannot be safely quoted.' % value) 1.1695 + if value.find('"""') == -1: 1.1696 + quot = tdquot 1.1697 + else: 1.1698 + quot = tsquot 1.1699 + return quot % value 1.1700 + 1.1701 + def _handle_value(self, value): 1.1702 + """ 1.1703 + Given a value string, unquote, remove comment, 1.1704 + handle lists. (including empty and single member lists) 1.1705 + """ 1.1706 + # do we look for lists in values ? 1.1707 + if not self.list_values: 1.1708 + mat = self._nolistvalue.match(value) 1.1709 + if mat is None: 1.1710 + raise SyntaxError 1.1711 + # NOTE: we don't unquote here 1.1712 + return mat.groups() 1.1713 + # 1.1714 + mat = self._valueexp.match(value) 1.1715 + if mat is None: 1.1716 + # the value is badly constructed, probably badly quoted, 1.1717 + # or an invalid list 1.1718 + raise SyntaxError 1.1719 + (list_values, single, empty_list, comment) = mat.groups() 1.1720 + if (list_values == '') and (single is None): 1.1721 + # change this if you want to accept empty values 1.1722 + raise SyntaxError 1.1723 + # NOTE: note there is no error handling from here if the regex 1.1724 + # is wrong: then incorrect values will slip through 1.1725 + if empty_list is not None: 1.1726 + # the single comma - meaning an empty list 1.1727 + return ([], comment) 1.1728 + if single is not None: 1.1729 + # handle empty values 1.1730 + if list_values and not single: 1.1731 + # FIXME: the '' is a workaround because our regex now matches 1.1732 + # '' at the end of a list if it has a trailing comma 1.1733 + single = None 1.1734 + else: 1.1735 + single = single or '""' 1.1736 + single = self._unquote(single) 1.1737 + if list_values == '': 1.1738 + # not a list value 1.1739 + return (single, comment) 1.1740 + the_list = self._listvalueexp.findall(list_values) 1.1741 + the_list = [self._unquote(val) for val in the_list] 1.1742 + if single is not None: 1.1743 + the_list += [single] 1.1744 + return (the_list, comment) 1.1745 + 1.1746 + def _multiline(self, value, infile, cur_index, maxline): 1.1747 + """Extract the value, where we are in a multiline situation.""" 1.1748 + quot = value[:3] 1.1749 + newvalue = value[3:] 1.1750 + single_line = self._triple_quote[quot][0] 1.1751 + multi_line = self._triple_quote[quot][1] 1.1752 + mat = single_line.match(value) 1.1753 + if mat is not None: 1.1754 + retval = list(mat.groups()) 1.1755 + retval.append(cur_index) 1.1756 + return retval 1.1757 + elif newvalue.find(quot) != -1: 1.1758 + # somehow the triple quote is missing 1.1759 + raise SyntaxError 1.1760 + # 1.1761 + while cur_index < maxline: 1.1762 + cur_index += 1 1.1763 + newvalue += '\n' 1.1764 + line = infile[cur_index] 1.1765 + if line.find(quot) == -1: 1.1766 + newvalue += line 1.1767 + else: 1.1768 + # end of multiline, process it 1.1769 + break 1.1770 + else: 1.1771 + # we've got to the end of the config, oops... 1.1772 + raise SyntaxError 1.1773 + mat = multi_line.match(line) 1.1774 + if mat is None: 1.1775 + # a badly formed line 1.1776 + raise SyntaxError 1.1777 + (value, comment) = mat.groups() 1.1778 + return (newvalue + value, comment, cur_index) 1.1779 + 1.1780 + def _handle_configspec(self, configspec): 1.1781 + """Parse the configspec.""" 1.1782 + # FIXME: Should we check that the configspec was created with the 1.1783 + # correct settings ? (i.e. ``list_values=False``) 1.1784 + if not isinstance(configspec, ConfigObj): 1.1785 + try: 1.1786 + configspec = ConfigObj( 1.1787 + configspec, 1.1788 + raise_errors=True, 1.1789 + file_error=True, 1.1790 + list_values=False) 1.1791 + except ConfigObjError, e: 1.1792 + # FIXME: Should these errors have a reference 1.1793 + # to the already parsed ConfigObj ? 1.1794 + raise ConfigspecError('Parsing configspec failed: %s' % e) 1.1795 + except IOError, e: 1.1796 + raise IOError('Reading configspec failed: %s' % e) 1.1797 + self._set_configspec_value(configspec, self) 1.1798 + 1.1799 + def _set_configspec_value(self, configspec, section): 1.1800 + """Used to recursively set configspec values.""" 1.1801 + if '__many__' in configspec.sections: 1.1802 + section.configspec['__many__'] = configspec['__many__'] 1.1803 + if len(configspec.sections) > 1: 1.1804 + # FIXME: can we supply any useful information here ? 1.1805 + raise RepeatSectionError 1.1806 + if hasattr(configspec, 'initial_comment'): 1.1807 + section._configspec_initial_comment = configspec.initial_comment 1.1808 + section._configspec_final_comment = configspec.final_comment 1.1809 + section._configspec_encoding = configspec.encoding 1.1810 + section._configspec_BOM = configspec.BOM 1.1811 + section._configspec_newlines = configspec.newlines 1.1812 + section._configspec_indent_type = configspec.indent_type 1.1813 + for entry in configspec.scalars: 1.1814 + section._configspec_comments[entry] = configspec.comments[entry] 1.1815 + section._configspec_inline_comments[entry] = ( 1.1816 + configspec.inline_comments[entry]) 1.1817 + section.configspec[entry] = configspec[entry] 1.1818 + section._order.append(entry) 1.1819 + for entry in configspec.sections: 1.1820 + if entry == '__many__': 1.1821 + continue 1.1822 + section._cs_section_comments[entry] = configspec.comments[entry] 1.1823 + section._cs_section_inline_comments[entry] = ( 1.1824 + configspec.inline_comments[entry]) 1.1825 + if not section.has_key(entry): 1.1826 + section[entry] = {} 1.1827 + self._set_configspec_value(configspec[entry], section[entry]) 1.1828 + 1.1829 + def _handle_repeat(self, section, configspec): 1.1830 + """Dynamically assign configspec for repeated section.""" 1.1831 + try: 1.1832 + section_keys = configspec.sections 1.1833 + scalar_keys = configspec.scalars 1.1834 + except AttributeError: 1.1835 + section_keys = [entry for entry in configspec 1.1836 + if isinstance(configspec[entry], dict)] 1.1837 + scalar_keys = [entry for entry in configspec 1.1838 + if not isinstance(configspec[entry], dict)] 1.1839 + if '__many__' in section_keys and len(section_keys) > 1: 1.1840 + # FIXME: can we supply any useful information here ? 1.1841 + raise RepeatSectionError 1.1842 + scalars = {} 1.1843 + sections = {} 1.1844 + for entry in scalar_keys: 1.1845 + val = configspec[entry] 1.1846 + scalars[entry] = val 1.1847 + for entry in section_keys: 1.1848 + val = configspec[entry] 1.1849 + if entry == '__many__': 1.1850 + scalars[entry] = val 1.1851 + continue 1.1852 + sections[entry] = val 1.1853 + # 1.1854 + section.configspec = scalars 1.1855 + for entry in sections: 1.1856 + if not section.has_key(entry): 1.1857 + section[entry] = {} 1.1858 + self._handle_repeat(section[entry], sections[entry]) 1.1859 + 1.1860 + def _write_line(self, indent_string, entry, this_entry, comment): 1.1861 + """Write an individual line, for the write method""" 1.1862 + # NOTE: the calls to self._quote here handles non-StringType values. 1.1863 + if not self.unrepr: 1.1864 + val = self._decode_element(self._quote(this_entry)) 1.1865 + else: 1.1866 + val = repr(this_entry) 1.1867 + return '%s%s%s%s%s' % ( 1.1868 + indent_string, 1.1869 + self._decode_element(self._quote(entry, multiline=False)), 1.1870 + self._a_to_u(' = '), 1.1871 + val, 1.1872 + self._decode_element(comment)) 1.1873 + 1.1874 + def _write_marker(self, indent_string, depth, entry, comment): 1.1875 + """Write a section marker line""" 1.1876 + return '%s%s%s%s%s' % ( 1.1877 + indent_string, 1.1878 + self._a_to_u('[' * depth), 1.1879 + self._quote(self._decode_element(entry), multiline=False), 1.1880 + self._a_to_u(']' * depth), 1.1881 + self._decode_element(comment)) 1.1882 + 1.1883 + def _handle_comment(self, comment): 1.1884 + """Deal with a comment.""" 1.1885 + if not comment: 1.1886 + return '' 1.1887 + start = self.indent_type 1.1888 + if not comment.startswith('#'): 1.1889 + start += self._a_to_u(' # ') 1.1890 + return (start + comment) 1.1891 + 1.1892 + # Public methods 1.1893 + 1.1894 + def write(self, outfile=None, section=None): 1.1895 + """ 1.1896 + Write the current ConfigObj as a file 1.1897 + 1.1898 + tekNico: FIXME: use StringIO instead of real files 1.1899 + 1.1900 + >>> filename = a.filename 1.1901 + >>> a.filename = 'test.ini' 1.1902 + >>> a.write() 1.1903 + >>> a.filename = filename 1.1904 + >>> a == ConfigObj('test.ini', raise_errors=True) 1.1905 + 1 1.1906 + """ 1.1907 + if self.indent_type is None: 1.1908 + # this can be true if initialised from a dictionary 1.1909 + self.indent_type = DEFAULT_INDENT_TYPE 1.1910 + # 1.1911 + out = [] 1.1912 + cs = self._a_to_u('#') 1.1913 + csp = self._a_to_u('# ') 1.1914 + if section is None: 1.1915 + int_val = self.interpolation 1.1916 + self.interpolation = False 1.1917 + section = self 1.1918 + for line in self.initial_comment: 1.1919 + line = self._decode_element(line) 1.1920 + stripped_line = line.strip() 1.1921 + if stripped_line and not stripped_line.startswith(cs): 1.1922 + line = csp + line 1.1923 + out.append(line) 1.1924 + # 1.1925 + indent_string = self.indent_type * section.depth 1.1926 + for entry in (section.scalars + section.sections): 1.1927 + if entry in section.defaults: 1.1928 + # don't write out default values 1.1929 + continue 1.1930 + for comment_line in section.comments[entry]: 1.1931 + comment_line = self._decode_element(comment_line.lstrip()) 1.1932 + if comment_line and not comment_line.startswith(cs): 1.1933 + comment_line = csp + comment_line 1.1934 + out.append(indent_string + comment_line) 1.1935 + this_entry = section[entry] 1.1936 + comment = self._handle_comment(section.inline_comments[entry]) 1.1937 + # 1.1938 + if isinstance(this_entry, dict): 1.1939 + # a section 1.1940 + out.append(self._write_marker( 1.1941 + indent_string, 1.1942 + this_entry.depth, 1.1943 + entry, 1.1944 + comment)) 1.1945 + out.extend(self.write(section=this_entry)) 1.1946 + else: 1.1947 + out.append(self._write_line( 1.1948 + indent_string, 1.1949 + entry, 1.1950 + this_entry, 1.1951 + comment)) 1.1952 + # 1.1953 + if section is self: 1.1954 + for line in self.final_comment: 1.1955 + line = self._decode_element(line) 1.1956 + stripped_line = line.strip() 1.1957 + if stripped_line and not stripped_line.startswith(cs): 1.1958 + line = csp + line 1.1959 + out.append(line) 1.1960 + self.interpolation = int_val 1.1961 + # 1.1962 + if section is not self: 1.1963 + return out 1.1964 + # 1.1965 + if (self.filename is None) and (outfile is None): 1.1966 + # output a list of lines 1.1967 + # might need to encode 1.1968 + # NOTE: This will *screw* UTF16, each line will start with the BOM 1.1969 + if self.encoding: 1.1970 + out = [l.encode(self.encoding) for l in out] 1.1971 + if (self.BOM and ((self.encoding is None) or 1.1972 + (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))): 1.1973 + # Add the UTF8 BOM 1.1974 + if not out: 1.1975 + out.append('') 1.1976 + out[0] = BOM_UTF8 + out[0] 1.1977 + return out 1.1978 + # 1.1979 + # Turn the list to a string, joined with correct newlines 1.1980 + output = (self._a_to_u(self.newlines or os.linesep) 1.1981 + ).join(out) 1.1982 + if self.encoding: 1.1983 + output = output.encode(self.encoding) 1.1984 + if (self.BOM and ((self.encoding is None) or 1.1985 + (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))): 1.1986 + # Add the UTF8 BOM 1.1987 + output = BOM_UTF8 + output 1.1988 + if outfile is not None: 1.1989 + outfile.write(output) 1.1990 + else: 1.1991 + h = open(self.filename, 'wb') 1.1992 + h.write(output) 1.1993 + h.close() 1.1994 + 1.1995 + def validate(self, validator, preserve_errors=False, copy=False, 1.1996 + section=None): 1.1997 + """ 1.1998 + Test the ConfigObj against a configspec. 1.1999 + 1.2000 + It uses the ``validator`` object from *validate.py*. 1.2001 + 1.2002 + To run ``validate`` on the current ConfigObj, call: :: 1.2003 + 1.2004 + test = config.validate(validator) 1.2005 + 1.2006 + (Normally having previously passed in the configspec when the ConfigObj 1.2007 + was created - you can dynamically assign a dictionary of checks to the 1.2008 + ``configspec`` attribute of a section though). 1.2009 + 1.2010 + It returns ``True`` if everything passes, or a dictionary of 1.2011 + pass/fails (True/False). If every member of a subsection passes, it 1.2012 + will just have the value ``True``. (It also returns ``False`` if all 1.2013 + members fail). 1.2014 + 1.2015 + In addition, it converts the values from strings to their native 1.2016 + types if their checks pass (and ``stringify`` is set). 1.2017 + 1.2018 + If ``preserve_errors`` is ``True`` (``False`` is default) then instead 1.2019 + of a marking a fail with a ``False``, it will preserve the actual 1.2020 + exception object. This can contain info about the reason for failure. 1.2021 + For example the ``VdtValueTooSmallError`` indeicates that the value 1.2022 + supplied was too small. If a value (or section) is missing it will 1.2023 + still be marked as ``False``. 1.2024 + 1.2025 + You must have the validate module to use ``preserve_errors=True``. 1.2026 + 1.2027 + You can then use the ``flatten_errors`` function to turn your nested 1.2028 + results dictionary into a flattened list of failures - useful for 1.2029 + displaying meaningful error messages. 1.2030 + """ 1.2031 + if section is None: 1.2032 + if self.configspec is None: 1.2033 + raise ValueError, 'No configspec supplied.' 1.2034 + if preserve_errors: 1.2035 + if VdtMissingValue is None: 1.2036 + raise ImportError('Missing validate module.') 1.2037 + section = self 1.2038 + # 1.2039 + spec_section = section.configspec 1.2040 + if copy and hasattr(section, '_configspec_initial_comment'): 1.2041 + section.initial_comment = section._configspec_initial_comment 1.2042 + section.final_comment = section._configspec_final_comment 1.2043 + section.encoding = section._configspec_encoding 1.2044 + section.BOM = section._configspec_BOM 1.2045 + section.newlines = section._configspec_newlines 1.2046 + section.indent_type = section._configspec_indent_type 1.2047 + if '__many__' in section.configspec: 1.2048 + many = spec_section['__many__'] 1.2049 + # dynamically assign the configspecs 1.2050 + # for the sections below 1.2051 + for entry in section.sections: 1.2052 + self._handle_repeat(section[entry], many) 1.2053 + # 1.2054 + out = {} 1.2055 + ret_true = True 1.2056 + ret_false = True 1.2057 + order = [k for k in section._order if k in spec_section] 1.2058 + order += [k for k in spec_section if k not in order] 1.2059 + for entry in order: 1.2060 + if entry == '__many__': 1.2061 + continue 1.2062 + if (not entry in section.scalars) or (entry in section.defaults): 1.2063 + # missing entries 1.2064 + # or entries from defaults 1.2065 + missing = True 1.2066 + val = None 1.2067 + if copy and not entry in section.scalars: 1.2068 + # copy comments 1.2069 + section.comments[entry] = ( 1.2070 + section._configspec_comments.get(entry, [])) 1.2071 + section.inline_comments[entry] = ( 1.2072 + section._configspec_inline_comments.get(entry, '')) 1.2073 + # 1.2074 + else: 1.2075 + missing = False 1.2076 + val = section[entry] 1.2077 + try: 1.2078 + check = validator.check(spec_section[entry], 1.2079 + val, 1.2080 + missing=missing 1.2081 + ) 1.2082 + except validator.baseErrorClass, e: 1.2083 + if not preserve_errors or isinstance(e, VdtMissingValue): 1.2084 + out[entry] = False 1.2085 + else: 1.2086 + # preserve the error 1.2087 + out[entry] = e 1.2088 + ret_false = False 1.2089 + ret_true = False 1.2090 + else: 1.2091 + ret_false = False 1.2092 + out[entry] = True 1.2093 + if self.stringify or missing: 1.2094 + # if we are doing type conversion 1.2095 + # or the value is a supplied default 1.2096 + if not self.stringify: 1.2097 + if isinstance(check, (list, tuple)): 1.2098 + # preserve lists 1.2099 + check = [self._str(item) for item in check] 1.2100 + elif missing and check is None: 1.2101 + # convert the None from a default to a '' 1.2102 + check = '' 1.2103 + else: 1.2104 + check = self._str(check) 1.2105 + if (check != val) or missing: 1.2106 + section[entry] = check 1.2107 + if not copy and missing and entry not in section.defaults: 1.2108 + section.defaults.append(entry) 1.2109 + # 1.2110 + # Missing sections will have been created as empty ones when the 1.2111 + # configspec was read. 1.2112 + for entry in section.sections: 1.2113 + # FIXME: this means DEFAULT is not copied in copy mode 1.2114 + if section is self and entry == 'DEFAULT': 1.2115 + continue 1.2116 + if copy: 1.2117 + section.comments[entry] = section._cs_section_comments[entry] 1.2118 + section.inline_comments[entry] = ( 1.2119 + section._cs_section_inline_comments[entry]) 1.2120 + check = self.validate(validator, preserve_errors=preserve_errors, 1.2121 + copy=copy, section=section[entry]) 1.2122 + out[entry] = check 1.2123 + if check == False: 1.2124 + ret_true = False 1.2125 + elif check == True: 1.2126 + ret_false = False 1.2127 + else: 1.2128 + ret_true = False 1.2129 + ret_false = False 1.2130 + # 1.2131 + if ret_true: 1.2132 + return True 1.2133 + elif ret_false: 1.2134 + return False 1.2135 + else: 1.2136 + return out 1.2137 + 1.2138 +class SimpleVal(object): 1.2139 + """ 1.2140 + A simple validator. 1.2141 + Can be used to check that all members expected are present. 1.2142 + 1.2143 + To use it, provide a configspec with all your members in (the value given 1.2144 + will be ignored). Pass an instance of ``SimpleVal`` to the ``validate`` 1.2145 + method of your ``ConfigObj``. ``validate`` will return ``True`` if all 1.2146 + members are present, or a dictionary with True/False meaning 1.2147 + present/missing. (Whole missing sections will be replaced with ``False``) 1.2148 + """ 1.2149 + 1.2150 + def __init__(self): 1.2151 + self.baseErrorClass = ConfigObjError 1.2152 + 1.2153 + def check(self, check, member, missing=False): 1.2154 + """A dummy check method, always returns the value unchanged.""" 1.2155 + if missing: 1.2156 + raise self.baseErrorClass 1.2157 + return member 1.2158 + 1.2159 +# Check / processing functions for options 1.2160 +def flatten_errors(cfg, res, levels=None, results=None): 1.2161 + """ 1.2162 + An example function that will turn a nested dictionary of results 1.2163 + (as returned by ``ConfigObj.validate``) into a flat list. 1.2164 + 1.2165 + ``cfg`` is the ConfigObj instance being checked, ``res`` is the results 1.2166 + dictionary returned by ``validate``. 1.2167 + 1.2168 + (This is a recursive function, so you shouldn't use the ``levels`` or 1.2169 + ``results`` arguments - they are used by the function. 1.2170 + 1.2171 + Returns a list of keys that failed. Each member of the list is a tuple : 1.2172 + :: 1.2173 + 1.2174 + ([list of sections...], key, result) 1.2175 + 1.2176 + If ``validate`` was called with ``preserve_errors=False`` (the default) 1.2177 + then ``result`` will always be ``False``. 1.2178 + 1.2179 + *list of sections* is a flattened list of sections that the key was found 1.2180 + in. 1.2181 + 1.2182 + If the section was missing then key will be ``None``. 1.2183 + 1.2184 + If the value (or section) was missing then ``result`` will be ``False``. 1.2185 + 1.2186 + If ``validate`` was called with ``preserve_errors=True`` and a value 1.2187 + was present, but failed the check, then ``result`` will be the exception 1.2188 + object returned. You can use this as a string that describes the failure. 1.2189 + 1.2190 + For example *The value "3" is of the wrong type*. 1.2191 + 1.2192 + >>> import validate 1.2193 + >>> vtor = validate.Validator() 1.2194 + >>> my_ini = ''' 1.2195 + ... option1 = True 1.2196 + ... [section1] 1.2197 + ... option1 = True 1.2198 + ... [section2] 1.2199 + ... another_option = Probably 1.2200 + ... [section3] 1.2201 + ... another_option = True 1.2202 + ... [[section3b]] 1.2203 + ... value = 3 1.2204 + ... value2 = a 1.2205 + ... value3 = 11 1.2206 + ... ''' 1.2207 + >>> my_cfg = ''' 1.2208 + ... option1 = boolean() 1.2209 + ... option2 = boolean() 1.2210 + ... option3 = boolean(default=Bad_value) 1.2211 + ... [section1] 1.2212 + ... option1 = boolean() 1.2213 + ... option2 = boolean() 1.2214 + ... option3 = boolean(default=Bad_value) 1.2215 + ... [section2] 1.2216 + ... another_option = boolean() 1.2217 + ... [section3] 1.2218 + ... another_option = boolean() 1.2219 + ... [[section3b]] 1.2220 + ... value = integer 1.2221 + ... value2 = integer 1.2222 + ... value3 = integer(0, 10) 1.2223 + ... [[[section3b-sub]]] 1.2224 + ... value = string 1.2225 + ... [section4] 1.2226 + ... another_option = boolean() 1.2227 + ... ''' 1.2228 + >>> cs = my_cfg.split('\\n') 1.2229 + >>> ini = my_ini.split('\\n') 1.2230 + >>> cfg = ConfigObj(ini, configspec=cs) 1.2231 + >>> res = cfg.validate(vtor, preserve_errors=True) 1.2232 + >>> errors = [] 1.2233 + >>> for entry in flatten_errors(cfg, res): 1.2234 + ... section_list, key, error = entry 1.2235 + ... section_list.insert(0, '[root]') 1.2236 + ... if key is not None: 1.2237 + ... section_list.append(key) 1.2238 + ... else: 1.2239 + ... section_list.append('[missing]') 1.2240 + ... section_string = ', '.join(section_list) 1.2241 + ... errors.append((section_string, ' = ', error)) 1.2242 + >>> errors.sort() 1.2243 + >>> for entry in errors: 1.2244 + ... print entry[0], entry[1], (entry[2] or 0) 1.2245 + [root], option2 = 0 1.2246 + [root], option3 = the value "Bad_value" is of the wrong type. 1.2247 + [root], section1, option2 = 0 1.2248 + [root], section1, option3 = the value "Bad_value" is of the wrong type. 1.2249 + [root], section2, another_option = the value "Probably" is of the wrong type. 1.2250 + [root], section3, section3b, section3b-sub, [missing] = 0 1.2251 + [root], section3, section3b, value2 = the value "a" is of the wrong type. 1.2252 + [root], section3, section3b, value3 = the value "11" is too big. 1.2253 + [root], section4, [missing] = 0 1.2254 + """ 1.2255 + if levels is None: 1.2256 + # first time called 1.2257 + levels = [] 1.2258 + results = [] 1.2259 + if res is True: 1.2260 + return results 1.2261 + if res is False: 1.2262 + results.append((levels[:], None, False)) 1.2263 + if levels: 1.2264 + levels.pop() 1.2265 + return results 1.2266 + for (key, val) in res.items(): 1.2267 + if val == True: 1.2268 + continue 1.2269 + if isinstance(cfg.get(key), dict): 1.2270 + # Go down one level 1.2271 + levels.append(key) 1.2272 + flatten_errors(cfg[key], val, levels, results) 1.2273 + continue 1.2274 + results.append((levels[:], key, val)) 1.2275 + # 1.2276 + # Go up one level 1.2277 + if levels: 1.2278 + levels.pop() 1.2279 + # 1.2280 + return results 1.2281 + 1.2282 +"""*A programming language is a medium of expression.* - Paul Graham"""