|
@ -1,5 +1,9 @@ |
|
|
# configobj.py |
|
|
# configobj.py |
|
|
# A config file reader/writer that supports nested sections in config files. |
|
|
# -*- coding: utf-8 -*- |
|
|
|
|
|
# pylint: disable=bad-continuation |
|
|
|
|
|
|
|
|
|
|
|
"""A config file reader/writer that supports nested sections in config files.""" |
|
|
|
|
|
|
|
|
# Copyright (C) 2005-2014: |
|
|
# Copyright (C) 2005-2014: |
|
|
# (name) : (email) |
|
|
# (name) : (email) |
|
|
# Michael Foord: fuzzyman AT voidspace DOT org DOT uk |
|
|
# Michael Foord: fuzzyman AT voidspace DOT org DOT uk |
|
@ -16,10 +20,17 @@ |
|
|
import os |
|
|
import os |
|
|
import re |
|
|
import re |
|
|
import sys |
|
|
import sys |
|
|
import collections |
|
|
import copy |
|
|
|
|
|
|
|
|
from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE |
|
|
from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE |
|
|
|
|
|
|
|
|
|
|
|
try: |
|
|
|
|
|
# Python 3 |
|
|
|
|
|
from collections.abc import Mapping |
|
|
|
|
|
except ImportError: |
|
|
|
|
|
# Python 2.7 |
|
|
|
|
|
from collections import Mapping |
|
|
|
|
|
|
|
|
import six |
|
|
import six |
|
|
from ._version import __version__ |
|
|
from ._version import __version__ |
|
|
|
|
|
|
|
@ -139,64 +150,6 @@ class UnknownType(Exception): |
|
|
pass |
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Builder(object): |
|
|
|
|
|
|
|
|
|
|
|
def build(self, o): |
|
|
|
|
|
if m is None: |
|
|
|
|
|
raise UnknownType(o.__class__.__name__) |
|
|
|
|
|
return m(o) |
|
|
|
|
|
|
|
|
|
|
|
def build_List(self, o): |
|
|
|
|
|
return list(map(self.build, o.getChildren())) |
|
|
|
|
|
|
|
|
|
|
|
def build_Const(self, o): |
|
|
|
|
|
return o.value |
|
|
|
|
|
|
|
|
|
|
|
def build_Dict(self, o): |
|
|
|
|
|
d = {} |
|
|
|
|
|
i = iter(map(self.build, o.getChildren())) |
|
|
|
|
|
for el in i: |
|
|
|
|
|
d[el] = next(i) |
|
|
|
|
|
return d |
|
|
|
|
|
|
|
|
|
|
|
def build_Tuple(self, o): |
|
|
|
|
|
return tuple(self.build_List(o)) |
|
|
|
|
|
|
|
|
|
|
|
def build_Name(self, o): |
|
|
|
|
|
if o.name == 'None': |
|
|
|
|
|
return None |
|
|
|
|
|
if o.name == 'True': |
|
|
|
|
|
return True |
|
|
|
|
|
if o.name == 'False': |
|
|
|
|
|
return False |
|
|
|
|
|
|
|
|
|
|
|
# An undefined Name |
|
|
|
|
|
raise UnknownType('Undefined Name') |
|
|
|
|
|
|
|
|
|
|
|
def build_Add(self, o): |
|
|
|
|
|
real, imag = list(map(self.build_Const, o.getChildren())) |
|
|
|
|
|
try: |
|
|
|
|
|
real = float(real) |
|
|
|
|
|
except TypeError: |
|
|
|
|
|
raise UnknownType('Add') |
|
|
|
|
|
if not isinstance(imag, complex) or imag.real != 0.0: |
|
|
|
|
|
raise UnknownType('Add') |
|
|
|
|
|
return real+imag |
|
|
|
|
|
|
|
|
|
|
|
def build_Getattr(self, o): |
|
|
|
|
|
parent = self.build(o.expr) |
|
|
|
|
|
return getattr(parent, o.attrname) |
|
|
|
|
|
|
|
|
|
|
|
def build_UnarySub(self, o): |
|
|
|
|
|
return -self.build_Const(o.getChildren()[0]) |
|
|
|
|
|
|
|
|
|
|
|
def build_UnaryAdd(self, o): |
|
|
|
|
|
return self.build_Const(o.getChildren()[0]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_builder = Builder() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def unrepr(s): |
|
|
def unrepr(s): |
|
|
if not s: |
|
|
if not s: |
|
|
return s |
|
|
return s |
|
@ -596,7 +549,7 @@ class Section(dict): |
|
|
if key not in self: |
|
|
if key not in self: |
|
|
self.sections.append(key) |
|
|
self.sections.append(key) |
|
|
dict.__setitem__(self, key, value) |
|
|
dict.__setitem__(self, key, value) |
|
|
elif isinstance(value, collections.Mapping) and not unrepr: |
|
|
elif isinstance(value, Mapping) and not unrepr: |
|
|
# First create the new depth level, |
|
|
# First create the new depth level, |
|
|
# then create the section |
|
|
# then create the section |
|
|
if key not in self: |
|
|
if key not in self: |
|
@ -709,34 +662,34 @@ class Section(dict): |
|
|
|
|
|
|
|
|
def items(self): |
|
|
def items(self): |
|
|
"""D.items() -> list of D's (key, value) pairs, as 2-tuples""" |
|
|
"""D.items() -> list of D's (key, value) pairs, as 2-tuples""" |
|
|
return list(zip((self.scalars + self.sections), list(self.values()))) |
|
|
return [(key, self[key]) for key in self.keys()] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def keys(self): |
|
|
def keys(self): |
|
|
"""D.keys() -> list of D's keys""" |
|
|
"""D.keys() -> list of D's keys""" |
|
|
return (self.scalars + self.sections) |
|
|
return self.scalars + self.sections |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def values(self): |
|
|
def values(self): |
|
|
"""D.values() -> list of D's values""" |
|
|
"""D.values() -> list of D's values""" |
|
|
return [self[key] for key in (self.scalars + self.sections)] |
|
|
return [self[key] for key in self.keys()] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def iteritems(self): |
|
|
def iteritems(self): |
|
|
"""D.iteritems() -> an iterator over the (key, value) items of D""" |
|
|
"""D.iteritems() -> an iterator over the (key, value) items of D""" |
|
|
return iter(list(self.items())) |
|
|
return iter(self.items()) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def iterkeys(self): |
|
|
def iterkeys(self): |
|
|
"""D.iterkeys() -> an iterator over the keys of D""" |
|
|
"""D.iterkeys() -> an iterator over the keys of D""" |
|
|
return iter((self.scalars + self.sections)) |
|
|
return iter(self.keys()) |
|
|
|
|
|
|
|
|
__iter__ = iterkeys |
|
|
__iter__ = iterkeys |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def itervalues(self): |
|
|
def itervalues(self): |
|
|
"""D.itervalues() -> an iterator over the values of D""" |
|
|
"""D.itervalues() -> an iterator over the values of D""" |
|
|
return iter(list(self.values())) |
|
|
return iter(self.values()) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __repr__(self): |
|
|
def __repr__(self): |
|
@ -746,7 +699,7 @@ class Section(dict): |
|
|
return self[key] |
|
|
return self[key] |
|
|
except MissingInterpolationOption: |
|
|
except MissingInterpolationOption: |
|
|
return dict.__getitem__(self, key) |
|
|
return dict.__getitem__(self, key) |
|
|
return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(_getval(key)))) |
|
|
return '{%s}' % ', '.join([('{}: {}'.format(repr(key), repr(_getval(key)))) |
|
|
for key in (self.scalars + self.sections)]) |
|
|
for key in (self.scalars + self.sections)]) |
|
|
|
|
|
|
|
|
__str__ = __repr__ |
|
|
__str__ = __repr__ |
|
@ -783,10 +736,15 @@ class Section(dict): |
|
|
return newdict |
|
|
return newdict |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def merge(self, indict): |
|
|
def merge(self, indict, decoupled=False): |
|
|
""" |
|
|
""" |
|
|
A recursive update - useful for merging config files. |
|
|
A recursive update - useful for merging config files. |
|
|
|
|
|
|
|
|
|
|
|
Note: if ``decoupled`` is ``True``, then the target object (self) |
|
|
|
|
|
gets its own copy of any mutable objects in the source dictionary |
|
|
|
|
|
(both sections and values), paid for by more work for ``merge()`` |
|
|
|
|
|
and more memory usage. |
|
|
|
|
|
|
|
|
>>> a = '''[section1] |
|
|
>>> a = '''[section1] |
|
|
... option1 = True |
|
|
... option1 = True |
|
|
... [[subsection]] |
|
|
... [[subsection]] |
|
@ -802,10 +760,12 @@ class Section(dict): |
|
|
>>> c2 |
|
|
>>> c2 |
|
|
ConfigObj({'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}) |
|
|
ConfigObj({'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}) |
|
|
""" |
|
|
""" |
|
|
for key, val in list(indict.items()): |
|
|
for key, val in indict.items(): |
|
|
if (key in self and isinstance(self[key], collections.Mapping) and |
|
|
if decoupled: |
|
|
isinstance(val, collections.Mapping)): |
|
|
val = copy.deepcopy(val) |
|
|
self[key].merge(val) |
|
|
if (key in self and isinstance(self[key], Mapping) and |
|
|
|
|
|
isinstance(val, Mapping)): |
|
|
|
|
|
self[key].merge(val, decoupled=decoupled) |
|
|
else: |
|
|
else: |
|
|
self[key] = val |
|
|
self[key] = val |
|
|
|
|
|
|
|
@ -1069,9 +1029,23 @@ class Section(dict): |
|
|
self[section].restore_defaults() |
|
|
self[section].restore_defaults() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_triple_quote(value): |
|
|
|
|
|
"""Helper for triple-quoting round-trips.""" |
|
|
|
|
|
if ('"""' in value) and ("'''" in value): |
|
|
|
|
|
raise ConfigObjError('Value cannot be safely quoted: {!r}'.format(value)) |
|
|
|
|
|
|
|
|
|
|
|
return tsquot if "'''" in value else tdquot |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ConfigObj(Section): |
|
|
class ConfigObj(Section): |
|
|
"""An object to read, create, and write config files.""" |
|
|
"""An object to read, create, and write config files.""" |
|
|
|
|
|
|
|
|
|
|
|
MAX_PARSE_ERROR_DETAILS = 5 |
|
|
|
|
|
|
|
|
|
|
|
# Override/append to this class variable for alternative comment markers |
|
|
|
|
|
# TODO: also support inline comments (needs dynamic compiling of the regex below) |
|
|
|
|
|
COMMENT_MARKERS = ['#'] |
|
|
|
|
|
|
|
|
_keyword = re.compile(r'''^ # line start |
|
|
_keyword = re.compile(r'''^ # line start |
|
|
(\s*) # indentation |
|
|
(\s*) # indentation |
|
|
( # keyword |
|
|
( # keyword |
|
@ -1094,7 +1068,7 @@ class ConfigObj(Section): |
|
|
(?:[^'"\s].*?) # at least one non-space unquoted |
|
|
(?:[^'"\s].*?) # at least one non-space unquoted |
|
|
) # section name close |
|
|
) # section name close |
|
|
((?:\s*\])+) # 4: section marker close |
|
|
((?:\s*\])+) # 4: section marker close |
|
|
\s*(\#.*)? # 5: optional comment |
|
|
(\s*(?:\#.*)?)? # 5: optional comment |
|
|
$''', |
|
|
$''', |
|
|
re.VERBOSE) |
|
|
re.VERBOSE) |
|
|
|
|
|
|
|
@ -1124,7 +1098,7 @@ class ConfigObj(Section): |
|
|
)| |
|
|
)| |
|
|
(,) # alternatively a single comma - empty list |
|
|
(,) # alternatively a single comma - empty list |
|
|
) |
|
|
) |
|
|
\s*(\#.*)? # optional comment |
|
|
(\s*(?:\#.*)?)? # optional comment |
|
|
$''', |
|
|
$''', |
|
|
re.VERBOSE) |
|
|
re.VERBOSE) |
|
|
|
|
|
|
|
@ -1148,15 +1122,16 @@ class ConfigObj(Section): |
|
|
(?:[^'"\#].*?)| # unquoted |
|
|
(?:[^'"\#].*?)| # unquoted |
|
|
(?:) # Empty value |
|
|
(?:) # Empty value |
|
|
) |
|
|
) |
|
|
\s*(\#.*)? # optional comment |
|
|
(\s*(?:\#.*)?)? # optional comment |
|
|
$''', |
|
|
$''', |
|
|
re.VERBOSE) |
|
|
re.VERBOSE) |
|
|
|
|
|
|
|
|
# regexes for finding triple quoted values on one line |
|
|
# regexes for finding triple quoted values on one line |
|
|
_single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$") |
|
|
_triple_trailer = r"(\s*(?:#.*)?)?$" |
|
|
_single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$') |
|
|
_single_line_single = re.compile(r"^'''(.*?)'''" + _triple_trailer) |
|
|
_multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$") |
|
|
_single_line_double = re.compile(r'^"""(.*?)"""' + _triple_trailer) |
|
|
_multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$') |
|
|
_multi_line_single = re.compile(r"^(.*?)'''" + _triple_trailer) |
|
|
|
|
|
_multi_line_double = re.compile(r'^(.*?)"""' + _triple_trailer) |
|
|
|
|
|
|
|
|
_triple_quote = { |
|
|
_triple_quote = { |
|
|
"'''": (_single_line_single, _multi_line_single), |
|
|
"'''": (_single_line_single, _multi_line_single), |
|
@ -1229,8 +1204,12 @@ class ConfigObj(Section): |
|
|
self._original_configspec = configspec |
|
|
self._original_configspec = configspec |
|
|
self._load(infile, configspec) |
|
|
self._load(infile, configspec) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _load(self, infile, configspec): |
|
|
def _load(self, infile, configspec): |
|
|
|
|
|
try: |
|
|
|
|
|
infile = infile.__fspath__() |
|
|
|
|
|
except AttributeError: |
|
|
|
|
|
pass |
|
|
|
|
|
|
|
|
if isinstance(infile, six.string_types): |
|
|
if isinstance(infile, six.string_types): |
|
|
self.filename = infile |
|
|
self.filename = infile |
|
|
if os.path.isfile(infile): |
|
|
if os.path.isfile(infile): |
|
@ -1281,7 +1260,7 @@ class ConfigObj(Section): |
|
|
# needs splitting into lines - but needs doing *after* decoding |
|
|
# needs splitting into lines - but needs doing *after* decoding |
|
|
# in case it's not an 8 bit encoding |
|
|
# in case it's not an 8 bit encoding |
|
|
else: |
|
|
else: |
|
|
raise TypeError('infile must be a filename, file like object, or list of lines.') |
|
|
raise TypeError('infile must be a path-like object, file like object, or list of lines.') |
|
|
|
|
|
|
|
|
if content: |
|
|
if content: |
|
|
# don't do it for the empty ConfigObj |
|
|
# don't do it for the empty ConfigObj |
|
@ -1305,10 +1284,14 @@ class ConfigObj(Section): |
|
|
self._parse(content) |
|
|
self._parse(content) |
|
|
# if we had any errors, now is the time to raise them |
|
|
# if we had any errors, now is the time to raise them |
|
|
if self._errors: |
|
|
if self._errors: |
|
|
info = "at line %s." % self._errors[0].line_number |
|
|
|
|
|
if len(self._errors) > 1: |
|
|
if len(self._errors) > 1: |
|
|
msg = "Parsing failed with several errors.\nFirst error %s" % info |
|
|
msg = ["Parsing failed with {} errors.".format(len(self._errors))] |
|
|
error = ConfigObjError(msg) |
|
|
for error in self._errors[:self.MAX_PARSE_ERROR_DETAILS]: |
|
|
|
|
|
msg.append(str(error)) |
|
|
|
|
|
if len(self._errors) > self.MAX_PARSE_ERROR_DETAILS: |
|
|
|
|
|
msg.append("{} more error(s)!" |
|
|
|
|
|
.format(len(self._errors) - self.MAX_PARSE_ERROR_DETAILS)) |
|
|
|
|
|
error = ConfigObjError('\n '.join(msg)) |
|
|
else: |
|
|
else: |
|
|
error = self._errors[0] |
|
|
error = self._errors[0] |
|
|
# set the errors attribute; it's a list of tuples: |
|
|
# set the errors attribute; it's a list of tuples: |
|
@ -1364,8 +1347,8 @@ class ConfigObj(Section): |
|
|
return self[key] |
|
|
return self[key] |
|
|
except MissingInterpolationOption: |
|
|
except MissingInterpolationOption: |
|
|
return dict.__getitem__(self, key) |
|
|
return dict.__getitem__(self, key) |
|
|
return ('%s({%s})' % (self.__class__.__name__, |
|
|
return ('{}({{{}}})'.format(self.__class__.__name__, |
|
|
', '.join([('%s: %s' % (repr(key), repr(_getval(key)))) |
|
|
', '.join([('{}: {}'.format(repr(key), repr(_getval(key)))) |
|
|
for key in (self.scalars + self.sections)]))) |
|
|
for key in (self.scalars + self.sections)]))) |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -1486,28 +1469,20 @@ class ConfigObj(Section): |
|
|
return self._decode(infile, 'utf-8') |
|
|
return self._decode(infile, 'utf-8') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _a_to_u(self, aString): |
|
|
|
|
|
"""Decode ASCII strings to unicode if a self.encoding is specified.""" |
|
|
|
|
|
if isinstance(aString, six.binary_type) and self.encoding: |
|
|
|
|
|
return aString.decode(self.encoding) |
|
|
|
|
|
else: |
|
|
|
|
|
return aString |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _decode(self, infile, encoding): |
|
|
def _decode(self, infile, encoding): |
|
|
""" |
|
|
""" |
|
|
Decode infile to unicode. Using the specified encoding. |
|
|
Decode infile to unicode. Using the specified encoding. |
|
|
|
|
|
|
|
|
if is a string, it also needs converting to a list. |
|
|
if is a string, it also needs converting to a list. |
|
|
""" |
|
|
""" |
|
|
if isinstance(infile, six.string_types): |
|
|
|
|
|
return infile.splitlines(True) |
|
|
|
|
|
if isinstance(infile, six.binary_type): |
|
|
if isinstance(infile, six.binary_type): |
|
|
# NOTE: Could raise a ``UnicodeDecodeError`` |
|
|
# NOTE: Could raise a ``UnicodeDecodeError`` |
|
|
if encoding: |
|
|
if encoding: |
|
|
return infile.decode(encoding).splitlines(True) |
|
|
return infile.decode(encoding).splitlines(True) |
|
|
else: |
|
|
else: |
|
|
return infile.splitlines(True) |
|
|
return infile.splitlines(True) |
|
|
|
|
|
if isinstance(infile, six.string_types): |
|
|
|
|
|
return infile.splitlines(True) |
|
|
|
|
|
|
|
|
if encoding: |
|
|
if encoding: |
|
|
for i, line in enumerate(infile): |
|
|
for i, line in enumerate(infile): |
|
@ -1553,6 +1528,7 @@ class ConfigObj(Section): |
|
|
maxline = len(infile) - 1 |
|
|
maxline = len(infile) - 1 |
|
|
cur_index = -1 |
|
|
cur_index = -1 |
|
|
reset_comment = False |
|
|
reset_comment = False |
|
|
|
|
|
comment_markers = tuple(self.COMMENT_MARKERS) |
|
|
|
|
|
|
|
|
while cur_index < maxline: |
|
|
while cur_index < maxline: |
|
|
if reset_comment: |
|
|
if reset_comment: |
|
@ -1561,7 +1537,7 @@ class ConfigObj(Section): |
|
|
line = infile[cur_index] |
|
|
line = infile[cur_index] |
|
|
sline = line.strip() |
|
|
sline = line.strip() |
|
|
# do we have anything on the line ? |
|
|
# do we have anything on the line ? |
|
|
if not sline or sline.startswith('#'): |
|
|
if not sline or sline.startswith(comment_markers): |
|
|
reset_comment = False |
|
|
reset_comment = False |
|
|
comment_list.append(line) |
|
|
comment_list.append(line) |
|
|
continue |
|
|
continue |
|
@ -1628,7 +1604,7 @@ class ConfigObj(Section): |
|
|
mat = self._keyword.match(line) |
|
|
mat = self._keyword.match(line) |
|
|
if mat is None: |
|
|
if mat is None: |
|
|
self._handle_error( |
|
|
self._handle_error( |
|
|
'Invalid line ({0!r}) (matched as neither section nor keyword)'.format(line), |
|
|
'Invalid line ({!r}) (matched as neither section nor keyword)'.format(line), |
|
|
ParseError, infile, cur_index) |
|
|
ParseError, infile, cur_index) |
|
|
else: |
|
|
else: |
|
|
# is a keyword value |
|
|
# is a keyword value |
|
@ -1651,26 +1627,24 @@ class ConfigObj(Section): |
|
|
comment = '' |
|
|
comment = '' |
|
|
try: |
|
|
try: |
|
|
value = unrepr(value) |
|
|
value = unrepr(value) |
|
|
except Exception as e: |
|
|
except Exception as cause: |
|
|
if type(e) == UnknownType: |
|
|
if isinstance(cause, UnknownType): |
|
|
msg = 'Unknown name or type in value' |
|
|
msg = 'Unknown name or type in value' |
|
|
else: |
|
|
else: |
|
|
msg = 'Parse error from unrepr-ing multiline value' |
|
|
msg = 'Parse error from unrepr-ing multiline value' |
|
|
self._handle_error(msg, UnreprError, infile, |
|
|
self._handle_error(msg, UnreprError, infile, cur_index) |
|
|
cur_index) |
|
|
|
|
|
continue |
|
|
continue |
|
|
else: |
|
|
else: |
|
|
if self.unrepr: |
|
|
if self.unrepr: |
|
|
comment = '' |
|
|
comment = '' |
|
|
try: |
|
|
try: |
|
|
value = unrepr(value) |
|
|
value = unrepr(value) |
|
|
except Exception as e: |
|
|
except Exception as cause: |
|
|
if isinstance(e, UnknownType): |
|
|
if isinstance(cause, UnknownType): |
|
|
msg = 'Unknown name or type in value' |
|
|
msg = 'Unknown name or type in value' |
|
|
else: |
|
|
else: |
|
|
msg = 'Parse error from unrepr-ing value' |
|
|
msg = 'Parse error from unrepr-ing value' |
|
|
self._handle_error(msg, UnreprError, infile, |
|
|
self._handle_error(msg, UnreprError, infile, cur_index) |
|
|
cur_index) |
|
|
|
|
|
continue |
|
|
continue |
|
|
else: |
|
|
else: |
|
|
# extract comment and lists |
|
|
# extract comment and lists |
|
@ -1736,7 +1710,7 @@ class ConfigObj(Section): |
|
|
""" |
|
|
""" |
|
|
line = infile[cur_index] |
|
|
line = infile[cur_index] |
|
|
cur_index += 1 |
|
|
cur_index += 1 |
|
|
message = '{0} at line {1}.'.format(text, cur_index) |
|
|
message = '{} at line {}.'.format(text, cur_index) |
|
|
error = ErrorClass(message, cur_index, line) |
|
|
error = ErrorClass(message, cur_index, line) |
|
|
if self.raise_errors: |
|
|
if self.raise_errors: |
|
|
# raise the error - parsing stops here |
|
|
# raise the error - parsing stops here |
|
@ -1810,7 +1784,7 @@ class ConfigObj(Section): |
|
|
# for normal values either single or double quotes will do |
|
|
# for normal values either single or double quotes will do |
|
|
elif '\n' in value: |
|
|
elif '\n' in value: |
|
|
# will only happen if multiline is off - e.g. '\n' in key |
|
|
# will only happen if multiline is off - e.g. '\n' in key |
|
|
raise ConfigObjError('Value "%s" cannot be safely quoted.' % value) |
|
|
raise ConfigObjError('Value cannot be safely quoted: {!r}'.format(value)) |
|
|
elif ((value[0] not in wspace_plus) and |
|
|
elif ((value[0] not in wspace_plus) and |
|
|
(value[-1] not in wspace_plus) and |
|
|
(value[-1] not in wspace_plus) and |
|
|
(',' not in value)): |
|
|
(',' not in value)): |
|
@ -1819,7 +1793,7 @@ class ConfigObj(Section): |
|
|
quot = self._get_single_quote(value) |
|
|
quot = self._get_single_quote(value) |
|
|
else: |
|
|
else: |
|
|
# if value has '\n' or "'" *and* '"', it will need triple quotes |
|
|
# if value has '\n' or "'" *and* '"', it will need triple quotes |
|
|
quot = self._get_triple_quote(value) |
|
|
quot = _get_triple_quote(value) |
|
|
|
|
|
|
|
|
if quot == noquot and '#' in value and self.list_values: |
|
|
if quot == noquot and '#' in value and self.list_values: |
|
|
quot = self._get_single_quote(value) |
|
|
quot = self._get_single_quote(value) |
|
@ -1829,7 +1803,7 @@ class ConfigObj(Section): |
|
|
|
|
|
|
|
|
def _get_single_quote(self, value): |
|
|
def _get_single_quote(self, value): |
|
|
if ("'" in value) and ('"' in value): |
|
|
if ("'" in value) and ('"' in value): |
|
|
raise ConfigObjError('Value "%s" cannot be safely quoted.' % value) |
|
|
raise ConfigObjError('Value cannot be safely quoted: {!r}'.format(value)) |
|
|
elif '"' in value: |
|
|
elif '"' in value: |
|
|
quot = squot |
|
|
quot = squot |
|
|
else: |
|
|
else: |
|
@ -1837,16 +1811,6 @@ class ConfigObj(Section): |
|
|
return quot |
|
|
return quot |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_triple_quote(self, value): |
|
|
|
|
|
if (value.find('"""') != -1) and (value.find("'''") != -1): |
|
|
|
|
|
raise ConfigObjError('Value "%s" cannot be safely quoted.' % value) |
|
|
|
|
|
if value.find('"""') == -1: |
|
|
|
|
|
quot = tdquot |
|
|
|
|
|
else: |
|
|
|
|
|
quot = tsquot |
|
|
|
|
|
return quot |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_value(self, value): |
|
|
def _handle_value(self, value): |
|
|
""" |
|
|
""" |
|
|
Given a value string, unquote, remove comment, |
|
|
Given a value string, unquote, remove comment, |
|
@ -1941,12 +1905,12 @@ class ConfigObj(Section): |
|
|
raise_errors=True, |
|
|
raise_errors=True, |
|
|
file_error=True, |
|
|
file_error=True, |
|
|
_inspec=True) |
|
|
_inspec=True) |
|
|
except ConfigObjError as e: |
|
|
except ConfigObjError as cause: |
|
|
# FIXME: Should these errors have a reference |
|
|
# FIXME: Should these errors have a reference |
|
|
# to the already parsed ConfigObj ? |
|
|
# to the already parsed ConfigObj ? |
|
|
raise ConfigspecError('Parsing configspec failed: %s' % e) |
|
|
raise ConfigspecError('Parsing configspec failed: %s' % cause) |
|
|
except IOError as e: |
|
|
except IOError as cause: |
|
|
raise IOError('Reading configspec failed: %s' % e) |
|
|
raise IOError('Reading configspec failed: %s' % cause) |
|
|
|
|
|
|
|
|
self.configspec = configspec |
|
|
self.configspec = configspec |
|
|
|
|
|
|
|
@ -1989,27 +1953,32 @@ class ConfigObj(Section): |
|
|
val = repr(this_entry) |
|
|
val = repr(this_entry) |
|
|
return '%s%s%s%s%s' % (indent_string, |
|
|
return '%s%s%s%s%s' % (indent_string, |
|
|
self._decode_element(self._quote(entry, multiline=False)), |
|
|
self._decode_element(self._quote(entry, multiline=False)), |
|
|
self._a_to_u(' = '), |
|
|
' = ', |
|
|
val, |
|
|
val, |
|
|
self._decode_element(comment)) |
|
|
self._decode_element(comment)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _write_marker(self, indent_string, depth, entry, comment): |
|
|
def _write_marker(self, indent_string, depth, entry, comment): |
|
|
"""Write a section marker line""" |
|
|
"""Write a section marker line""" |
|
|
|
|
|
entry_str = self._decode_element(entry) |
|
|
|
|
|
title = self._quote(entry_str, multiline=False) |
|
|
|
|
|
if entry_str and title[0] in '\'"' and title[1:-1] == entry_str: |
|
|
|
|
|
# titles are in '[]' already, so quoting for contained quotes is not necessary (#74) |
|
|
|
|
|
title = entry_str |
|
|
return '%s%s%s%s%s' % (indent_string, |
|
|
return '%s%s%s%s%s' % (indent_string, |
|
|
self._a_to_u('[' * depth), |
|
|
'[' * depth, |
|
|
self._quote(self._decode_element(entry), multiline=False), |
|
|
title, |
|
|
self._a_to_u(']' * depth), |
|
|
']' * depth, |
|
|
self._decode_element(comment)) |
|
|
self._decode_element(comment)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_comment(self, comment): |
|
|
def _handle_comment(self, comment): |
|
|
"""Deal with a comment.""" |
|
|
"""Deal with a comment.""" |
|
|
if not comment: |
|
|
if not comment.strip(): |
|
|
return '' |
|
|
return comment or '' # return trailing whitespace as-is |
|
|
start = self.indent_type |
|
|
start = self.indent_type |
|
|
if not comment.startswith('#'): |
|
|
if not comment.lstrip().startswith('#'): |
|
|
start += self._a_to_u(' # ') |
|
|
start += ' # ' |
|
|
return (start + comment) |
|
|
return (start + comment) |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -2035,8 +2004,8 @@ class ConfigObj(Section): |
|
|
self.indent_type = DEFAULT_INDENT_TYPE |
|
|
self.indent_type = DEFAULT_INDENT_TYPE |
|
|
|
|
|
|
|
|
out = [] |
|
|
out = [] |
|
|
cs = self._a_to_u('#') |
|
|
comment_markers = tuple(self.COMMENT_MARKERS) |
|
|
csp = self._a_to_u('# ') |
|
|
comment_marker_default = comment_markers[0] + ' ' |
|
|
if section is None: |
|
|
if section is None: |
|
|
int_val = self.interpolation |
|
|
int_val = self.interpolation |
|
|
self.interpolation = False |
|
|
self.interpolation = False |
|
@ -2044,8 +2013,8 @@ class ConfigObj(Section): |
|
|
for line in self.initial_comment: |
|
|
for line in self.initial_comment: |
|
|
line = self._decode_element(line) |
|
|
line = self._decode_element(line) |
|
|
stripped_line = line.strip() |
|
|
stripped_line = line.strip() |
|
|
if stripped_line and not stripped_line.startswith(cs): |
|
|
if stripped_line and not stripped_line.startswith(comment_markers): |
|
|
line = csp + line |
|
|
line = comment_marker_default + line |
|
|
out.append(line) |
|
|
out.append(line) |
|
|
|
|
|
|
|
|
indent_string = self.indent_type * section.depth |
|
|
indent_string = self.indent_type * section.depth |
|
@ -2055,8 +2024,8 @@ class ConfigObj(Section): |
|
|
continue |
|
|
continue |
|
|
for comment_line in section.comments[entry]: |
|
|
for comment_line in section.comments[entry]: |
|
|
comment_line = self._decode_element(comment_line.lstrip()) |
|
|
comment_line = self._decode_element(comment_line.lstrip()) |
|
|
if comment_line and not comment_line.startswith(cs): |
|
|
if comment_line and not comment_line.startswith(comment_markers): |
|
|
comment_line = csp + comment_line |
|
|
comment_line = comment_marker_default + comment_line |
|
|
out.append(indent_string + comment_line) |
|
|
out.append(indent_string + comment_line) |
|
|
this_entry = section[entry] |
|
|
this_entry = section[entry] |
|
|
comment = self._handle_comment(section.inline_comments[entry]) |
|
|
comment = self._handle_comment(section.inline_comments[entry]) |
|
@ -2080,8 +2049,8 @@ class ConfigObj(Section): |
|
|
for line in self.final_comment: |
|
|
for line in self.final_comment: |
|
|
line = self._decode_element(line) |
|
|
line = self._decode_element(line) |
|
|
stripped_line = line.strip() |
|
|
stripped_line = line.strip() |
|
|
if stripped_line and not stripped_line.startswith(cs): |
|
|
if stripped_line and not stripped_line.startswith(comment_markers): |
|
|
line = csp + line |
|
|
line = comment_marker_default + line |
|
|
out.append(line) |
|
|
out.append(line) |
|
|
self.interpolation = int_val |
|
|
self.interpolation = int_val |
|
|
|
|
|
|
|
@ -2108,7 +2077,7 @@ class ConfigObj(Section): |
|
|
and sys.platform == 'win32' and newline == '\r\n'): |
|
|
and sys.platform == 'win32' and newline == '\r\n'): |
|
|
# Windows specific hack to avoid writing '\r\r\n' |
|
|
# Windows specific hack to avoid writing '\r\r\n' |
|
|
newline = '\n' |
|
|
newline = '\n' |
|
|
output = self._a_to_u(newline).join(out) |
|
|
output = newline.join(out) |
|
|
if not output.endswith(newline): |
|
|
if not output.endswith(newline): |
|
|
output += newline |
|
|
output += newline |
|
|
|
|
|
|
|
@ -2171,7 +2140,7 @@ class ConfigObj(Section): |
|
|
if preserve_errors: |
|
|
if preserve_errors: |
|
|
# We do this once to remove a top level dependency on the validate module |
|
|
# We do this once to remove a top level dependency on the validate module |
|
|
# Which makes importing configobj faster |
|
|
# Which makes importing configobj faster |
|
|
from validate import VdtMissingValue |
|
|
from configobj.validate import VdtMissingValue |
|
|
self._vdtMissingValue = VdtMissingValue |
|
|
self._vdtMissingValue = VdtMissingValue |
|
|
|
|
|
|
|
|
section = self |
|
|
section = self |
|
@ -2205,12 +2174,12 @@ class ConfigObj(Section): |
|
|
val, |
|
|
val, |
|
|
missing=missing |
|
|
missing=missing |
|
|
) |
|
|
) |
|
|
except validator.baseErrorClass as e: |
|
|
except validator.baseErrorClass as cause: |
|
|
if not preserve_errors or isinstance(e, self._vdtMissingValue): |
|
|
if not preserve_errors or isinstance(cause, self._vdtMissingValue): |
|
|
out[entry] = False |
|
|
out[entry] = False |
|
|
else: |
|
|
else: |
|
|
# preserve the error |
|
|
# preserve the error |
|
|
out[entry] = e |
|
|
out[entry] = cause |
|
|
ret_false = False |
|
|
ret_false = False |
|
|
ret_true = False |
|
|
ret_true = False |
|
|
else: |
|
|
else: |
|
@ -2441,7 +2410,7 @@ def flatten_errors(cfg, res, levels=None, results=None): |
|
|
for (key, val) in list(res.items()): |
|
|
for (key, val) in list(res.items()): |
|
|
if val == True: |
|
|
if val == True: |
|
|
continue |
|
|
continue |
|
|
if isinstance(cfg.get(key), collections.Mapping): |
|
|
if isinstance(cfg.get(key), Mapping): |
|
|
# Go down one level |
|
|
# Go down one level |
|
|
levels.append(key) |
|
|
levels.append(key) |
|
|
flatten_errors(cfg[key], val, levels, results) |
|
|
flatten_errors(cfg[key], val, levels, results) |
|
|