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