mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-06 05:01:14 -07:00
Update configobj to 5.1.0
This commit is contained in:
parent
f2d7beec90
commit
183c810c76
3 changed files with 1715 additions and 254 deletions
|
@ -1,29 +1,38 @@
|
||||||
# configobj.py
|
# configobj.py
|
||||||
# A config file reader/writer that supports nested sections in config files.
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (C) 2005-2010 Michael Foord, Nicola Larosa
|
# pylint: disable=bad-continuation
|
||||||
# E-mail: fuzzyman AT voidspace DOT org DOT uk
|
|
||||||
# nico AT tekNico DOT net
|
|
||||||
|
|
||||||
# ConfigObj 4
|
"""A config file reader/writer that supports nested sections in config files."""
|
||||||
# http://www.voidspace.org.uk/python/configobj.html
|
|
||||||
|
|
||||||
# Released subject to the BSD License
|
# Copyright (C) 2005-2014:
|
||||||
# Please see http://www.voidspace.org.uk/python/license.shtml
|
# (name) : (email)
|
||||||
|
# Michael Foord: fuzzyman AT voidspace DOT org DOT uk
|
||||||
|
# Nicola Larosa: nico AT tekNico DOT net
|
||||||
|
# Rob Dennis: rdennis AT gmail DOT com
|
||||||
|
# Eli Courtwright: eli AT courtwright DOT org
|
||||||
|
|
||||||
# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
|
# This software is licensed under the terms of the BSD license.
|
||||||
# For information about bugfixes, updates and support, please join the
|
# http://opensource.org/licenses/BSD-3-Clause
|
||||||
# ConfigObj mailing list:
|
|
||||||
# http://lists.sourceforge.net/lists/listinfo/configobj-develop
|
|
||||||
# Comments, suggestions and bug reports welcome.
|
|
||||||
|
|
||||||
from __future__ import generators
|
# ConfigObj 5 - main repository for documentation and issue tracking:
|
||||||
|
# https://github.com/DiffSK/configobj
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
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
|
||||||
|
from ._version import __version__
|
||||||
|
|
||||||
# imported lazily to avoid startup performance hit if it isn't used
|
# imported lazily to avoid startup performance hit if it isn't used
|
||||||
compiler = None
|
compiler = None
|
||||||
|
@ -83,20 +92,7 @@ tdquot = "'''%s'''"
|
||||||
# Sentinel for use in getattr calls to replace hasattr
|
# Sentinel for use in getattr calls to replace hasattr
|
||||||
MISSING = object()
|
MISSING = object()
|
||||||
|
|
||||||
__version__ = '4.7.2'
|
|
||||||
|
|
||||||
try:
|
|
||||||
any
|
|
||||||
except NameError:
|
|
||||||
def any(iterable):
|
|
||||||
for entry in iterable:
|
|
||||||
if entry:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'__version__',
|
|
||||||
'DEFAULT_INDENT_TYPE',
|
'DEFAULT_INDENT_TYPE',
|
||||||
'DEFAULT_INTERPOLATION',
|
'DEFAULT_INTERPOLATION',
|
||||||
'ConfigObjError',
|
'ConfigObjError',
|
||||||
|
@ -137,6 +133,8 @@ OPTION_DEFAULTS = {
|
||||||
'write_empty_values': False,
|
'write_empty_values': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# this could be replaced if six is used for compatibility, or there are no
|
||||||
|
# more assertions about items being a string
|
||||||
|
|
||||||
|
|
||||||
def getObj(s):
|
def getObj(s):
|
||||||
|
@ -152,70 +150,13 @@ class UnknownType(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Builder(object):
|
|
||||||
|
|
||||||
def build(self, o):
|
|
||||||
m = getattr(self, 'build_' + o.__class__.__name__, None)
|
|
||||||
if m is None:
|
|
||||||
raise UnknownType(o.__class__.__name__)
|
|
||||||
return m(o)
|
|
||||||
|
|
||||||
def build_List(self, o):
|
|
||||||
return 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] = i.next()
|
|
||||||
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 = 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
|
||||||
return _builder.build(getObj(s))
|
|
||||||
|
|
||||||
|
# this is supposed to be safe
|
||||||
|
import ast
|
||||||
|
return ast.literal_eval(s)
|
||||||
|
|
||||||
|
|
||||||
class ConfigObjError(SyntaxError):
|
class ConfigObjError(SyntaxError):
|
||||||
|
@ -518,7 +459,7 @@ class Section(dict):
|
||||||
self._initialise()
|
self._initialise()
|
||||||
# we do this explicitly so that __setitem__ is used properly
|
# we do this explicitly so that __setitem__ is used properly
|
||||||
# (rather than just passing to ``dict.__init__``)
|
# (rather than just passing to ``dict.__init__``)
|
||||||
for entry, value in indict.iteritems():
|
for entry, value in indict.items():
|
||||||
self[entry] = value
|
self[entry] = value
|
||||||
|
|
||||||
|
|
||||||
|
@ -566,11 +507,11 @@ class Section(dict):
|
||||||
"""Fetch the item and do string interpolation."""
|
"""Fetch the item and do string interpolation."""
|
||||||
val = dict.__getitem__(self, key)
|
val = dict.__getitem__(self, key)
|
||||||
if self.main.interpolation:
|
if self.main.interpolation:
|
||||||
if isinstance(val, basestring):
|
if isinstance(val, six.string_types):
|
||||||
return self._interpolate(key, val)
|
return self._interpolate(key, val)
|
||||||
if isinstance(val, list):
|
if isinstance(val, list):
|
||||||
def _check(entry):
|
def _check(entry):
|
||||||
if isinstance(entry, basestring):
|
if isinstance(entry, six.string_types):
|
||||||
return self._interpolate(key, entry)
|
return self._interpolate(key, entry)
|
||||||
return entry
|
return entry
|
||||||
new = [_check(entry) for entry in val]
|
new = [_check(entry) for entry in val]
|
||||||
|
@ -593,7 +534,7 @@ class Section(dict):
|
||||||
``unrepr`` must be set when setting a value to a dictionary, without
|
``unrepr`` must be set when setting a value to a dictionary, without
|
||||||
creating a new sub-section.
|
creating a new sub-section.
|
||||||
"""
|
"""
|
||||||
if not isinstance(key, basestring):
|
if not isinstance(key, six.string_types):
|
||||||
raise ValueError('The key "%s" is not a string.' % key)
|
raise ValueError('The key "%s" is not a string.' % key)
|
||||||
|
|
||||||
# add the comment
|
# add the comment
|
||||||
|
@ -608,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, dict) 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:
|
||||||
|
@ -627,11 +568,11 @@ class Section(dict):
|
||||||
if key not in self:
|
if key not in self:
|
||||||
self.scalars.append(key)
|
self.scalars.append(key)
|
||||||
if not self.main.stringify:
|
if not self.main.stringify:
|
||||||
if isinstance(value, basestring):
|
if isinstance(value, six.string_types):
|
||||||
pass
|
pass
|
||||||
elif isinstance(value, (list, tuple)):
|
elif isinstance(value, (list, tuple)):
|
||||||
for entry in value:
|
for entry in value:
|
||||||
if not isinstance(entry, basestring):
|
if not isinstance(entry, six.string_types):
|
||||||
raise TypeError('Value is not a string "%s".' % entry)
|
raise TypeError('Value is not a string "%s".' % entry)
|
||||||
else:
|
else:
|
||||||
raise TypeError('Value is not a string "%s".' % value)
|
raise TypeError('Value is not a string "%s".' % value)
|
||||||
|
@ -721,17 +662,17 @@ 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 zip((self.scalars + self.sections), 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):
|
||||||
|
@ -741,7 +682,7 @@ class Section(dict):
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
@ -758,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__
|
||||||
|
@ -795,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]]
|
||||||
|
@ -815,9 +761,11 @@ class Section(dict):
|
||||||
ConfigObj({'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}})
|
ConfigObj({'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}})
|
||||||
"""
|
"""
|
||||||
for key, val in indict.items():
|
for key, val in indict.items():
|
||||||
if (key in self and isinstance(self[key], dict) and
|
if decoupled:
|
||||||
isinstance(val, dict)):
|
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
|
||||||
|
|
||||||
|
@ -972,7 +920,7 @@ class Section(dict):
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
if not isinstance(val, basestring):
|
if not isinstance(val, six.string_types):
|
||||||
# TODO: Why do we raise a KeyError here?
|
# TODO: Why do we raise a KeyError here?
|
||||||
raise KeyError()
|
raise KeyError()
|
||||||
else:
|
else:
|
||||||
|
@ -1013,15 +961,15 @@ class Section(dict):
|
||||||
|
|
||||||
>>> a = ConfigObj()
|
>>> a = ConfigObj()
|
||||||
>>> a['a'] = 'fish'
|
>>> a['a'] = 'fish'
|
||||||
>>> a.as_float('a')
|
>>> a.as_float('a') #doctest: +IGNORE_EXCEPTION_DETAIL
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
ValueError: invalid literal for float(): fish
|
ValueError: invalid literal for float(): fish
|
||||||
>>> a['b'] = '1'
|
>>> a['b'] = '1'
|
||||||
>>> a.as_float('b')
|
>>> a.as_float('b')
|
||||||
1.0
|
1.0
|
||||||
>>> a['b'] = '3.2'
|
>>> a['b'] = '3.2'
|
||||||
>>> a.as_float('b')
|
>>> a.as_float('b') #doctest: +ELLIPSIS
|
||||||
3.2000000000000002
|
3.2...
|
||||||
"""
|
"""
|
||||||
return float(self[key])
|
return float(self[key])
|
||||||
|
|
||||||
|
@ -1081,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
|
||||||
|
@ -1106,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)
|
||||||
|
|
||||||
|
@ -1136,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)
|
||||||
|
|
||||||
|
@ -1145,7 +1107,7 @@ class ConfigObj(Section):
|
||||||
(
|
(
|
||||||
(?:".*?")| # double quotes
|
(?:".*?")| # double quotes
|
||||||
(?:'.*?')| # single quotes
|
(?:'.*?')| # single quotes
|
||||||
(?:[^'",\#]?.*?) # unquoted
|
(?:[^'",\#]?.*?) # unquoted
|
||||||
)
|
)
|
||||||
\s*,\s* # comma
|
\s*,\s* # comma
|
||||||
''',
|
''',
|
||||||
|
@ -1160,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),
|
||||||
|
@ -1218,13 +1181,13 @@ class ConfigObj(Section):
|
||||||
import warnings
|
import warnings
|
||||||
warnings.warn('Passing in an options dictionary to ConfigObj() is '
|
warnings.warn('Passing in an options dictionary to ConfigObj() is '
|
||||||
'deprecated. Use **options instead.',
|
'deprecated. Use **options instead.',
|
||||||
DeprecationWarning, stacklevel=2)
|
DeprecationWarning)
|
||||||
|
|
||||||
# TODO: check the values too.
|
# TODO: check the values too.
|
||||||
for entry in options:
|
for entry in options:
|
||||||
if entry not in OPTION_DEFAULTS:
|
if entry not in OPTION_DEFAULTS:
|
||||||
raise TypeError('Unrecognised option "%s".' % entry)
|
raise TypeError('Unrecognised option "%s".' % entry)
|
||||||
for entry, value in OPTION_DEFAULTS.items():
|
for entry, value in list(OPTION_DEFAULTS.items()):
|
||||||
if entry not in options:
|
if entry not in options:
|
||||||
options[entry] = value
|
options[entry] = value
|
||||||
keyword_value = _options[entry]
|
keyword_value = _options[entry]
|
||||||
|
@ -1241,14 +1204,17 @@ 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):
|
||||||
if isinstance(infile, basestring):
|
try:
|
||||||
|
infile = infile.__fspath__()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if isinstance(infile, six.string_types):
|
||||||
self.filename = infile
|
self.filename = infile
|
||||||
if os.path.isfile(infile):
|
if os.path.isfile(infile):
|
||||||
h = open(infile, 'rb')
|
with open(infile, 'rb') as h:
|
||||||
infile = h.read() or []
|
content = h.readlines() or []
|
||||||
h.close()
|
|
||||||
elif self.file_error:
|
elif self.file_error:
|
||||||
# raise an error if the file doesn't exist
|
# raise an error if the file doesn't exist
|
||||||
raise IOError('Config file not found: "%s".' % self.filename)
|
raise IOError('Config file not found: "%s".' % self.filename)
|
||||||
|
@ -1257,13 +1223,12 @@ class ConfigObj(Section):
|
||||||
if self.create_empty:
|
if self.create_empty:
|
||||||
# this is a good test that the filename specified
|
# this is a good test that the filename specified
|
||||||
# isn't impossible - like on a non-existent device
|
# isn't impossible - like on a non-existent device
|
||||||
h = open(infile, 'w')
|
with open(infile, 'w') as h:
|
||||||
h.write('')
|
h.write('')
|
||||||
h.close()
|
content = []
|
||||||
infile = []
|
|
||||||
|
|
||||||
elif isinstance(infile, (list, tuple)):
|
elif isinstance(infile, (list, tuple)):
|
||||||
infile = list(infile)
|
content = list(infile)
|
||||||
|
|
||||||
elif isinstance(infile, dict):
|
elif isinstance(infile, dict):
|
||||||
# initialise self
|
# initialise self
|
||||||
|
@ -1291,21 +1256,21 @@ class ConfigObj(Section):
|
||||||
|
|
||||||
elif getattr(infile, 'read', MISSING) is not MISSING:
|
elif getattr(infile, 'read', MISSING) is not MISSING:
|
||||||
# This supports file like objects
|
# This supports file like objects
|
||||||
infile = infile.read() or []
|
content = infile.read() or []
|
||||||
# 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 infile:
|
if content:
|
||||||
# don't do it for the empty ConfigObj
|
# don't do it for the empty ConfigObj
|
||||||
infile = self._handle_bom(infile)
|
content = self._handle_bom(content)
|
||||||
# infile is now *always* a list
|
# infile is now *always* a list
|
||||||
#
|
#
|
||||||
# Set the newlines attribute (first line ending it finds)
|
# Set the newlines attribute (first line ending it finds)
|
||||||
# and strip trailing '\n' or '\r' from lines
|
# and strip trailing '\n' or '\r' from lines
|
||||||
for line in infile:
|
for line in content:
|
||||||
if (not line) or (line[-1] not in ('\r', '\n', '\r\n')):
|
if (not line) or (line[-1] not in ('\r', '\n')):
|
||||||
continue
|
continue
|
||||||
for end in ('\r\n', '\n', '\r'):
|
for end in ('\r\n', '\n', '\r'):
|
||||||
if line.endswith(end):
|
if line.endswith(end):
|
||||||
|
@ -1313,15 +1278,20 @@ class ConfigObj(Section):
|
||||||
break
|
break
|
||||||
break
|
break
|
||||||
|
|
||||||
infile = [line.rstrip('\r\n') for line in infile]
|
assert all(isinstance(line, six.string_types) for line in content), repr(content)
|
||||||
|
content = [line.rstrip('\r\n') for line in content]
|
||||||
|
|
||||||
self._parse(infile)
|
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:
|
||||||
|
@ -1377,9 +1347,9 @@ class ConfigObj(Section):
|
||||||
return self[key]
|
return self[key]
|
||||||
except MissingInterpolationOption:
|
except MissingInterpolationOption:
|
||||||
return dict.__getitem__(self, key)
|
return dict.__getitem__(self, key)
|
||||||
return ('ConfigObj({%s})' %
|
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)])))
|
||||||
|
|
||||||
|
|
||||||
def _handle_bom(self, infile):
|
def _handle_bom(self, infile):
|
||||||
|
@ -1404,6 +1374,7 @@ class ConfigObj(Section):
|
||||||
``infile`` must always be returned as a list of lines, but may be
|
``infile`` must always be returned as a list of lines, but may be
|
||||||
passed in as a single string.
|
passed in as a single string.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if ((self.encoding is not None) and
|
if ((self.encoding is not None) and
|
||||||
(self.encoding.lower() not in BOM_LIST)):
|
(self.encoding.lower() not in BOM_LIST)):
|
||||||
# No need to check for a BOM
|
# No need to check for a BOM
|
||||||
|
@ -1415,6 +1386,13 @@ class ConfigObj(Section):
|
||||||
line = infile[0]
|
line = infile[0]
|
||||||
else:
|
else:
|
||||||
line = infile
|
line = infile
|
||||||
|
|
||||||
|
if isinstance(line, six.text_type):
|
||||||
|
# it's already decoded and there's no need to do anything
|
||||||
|
# else, just use the _decode utility method to handle
|
||||||
|
# listifying appropriately
|
||||||
|
return self._decode(infile, self.encoding)
|
||||||
|
|
||||||
if self.encoding is not None:
|
if self.encoding is not None:
|
||||||
# encoding explicitly supplied
|
# encoding explicitly supplied
|
||||||
# And it could have an associated BOM
|
# And it could have an associated BOM
|
||||||
|
@ -1423,7 +1401,7 @@ class ConfigObj(Section):
|
||||||
enc = BOM_LIST[self.encoding.lower()]
|
enc = BOM_LIST[self.encoding.lower()]
|
||||||
if enc == 'utf_16':
|
if enc == 'utf_16':
|
||||||
# For UTF16 we try big endian and little endian
|
# For UTF16 we try big endian and little endian
|
||||||
for BOM, (encoding, final_encoding) in BOMS.items():
|
for BOM, (encoding, final_encoding) in list(BOMS.items()):
|
||||||
if not final_encoding:
|
if not final_encoding:
|
||||||
# skip UTF8
|
# skip UTF8
|
||||||
continue
|
continue
|
||||||
|
@ -1453,8 +1431,9 @@ class ConfigObj(Section):
|
||||||
return self._decode(infile, self.encoding)
|
return self._decode(infile, self.encoding)
|
||||||
|
|
||||||
# No encoding specified - so we need to check for UTF8/UTF16
|
# No encoding specified - so we need to check for UTF8/UTF16
|
||||||
for BOM, (encoding, final_encoding) in BOMS.items():
|
for BOM, (encoding, final_encoding) in list(BOMS.items()):
|
||||||
if not line.startswith(BOM):
|
if not isinstance(line, six.binary_type) or not line.startswith(BOM):
|
||||||
|
# didn't specify a BOM, or it's not a bytestring
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
# BOM discovered
|
# BOM discovered
|
||||||
|
@ -1468,27 +1447,26 @@ class ConfigObj(Section):
|
||||||
infile[0] = newline
|
infile[0] = newline
|
||||||
else:
|
else:
|
||||||
infile = newline
|
infile = newline
|
||||||
# UTF8 - don't decode
|
# UTF-8
|
||||||
if isinstance(infile, basestring):
|
if isinstance(infile, six.text_type):
|
||||||
return infile.splitlines(True)
|
return infile.splitlines(True)
|
||||||
|
elif isinstance(infile, six.binary_type):
|
||||||
|
return infile.decode('utf-8').splitlines(True)
|
||||||
else:
|
else:
|
||||||
return infile
|
return self._decode(infile, 'utf-8')
|
||||||
# UTF16 - have to decode
|
# UTF16 - have to decode
|
||||||
return self._decode(infile, encoding)
|
return self._decode(infile, encoding)
|
||||||
|
|
||||||
# No BOM discovered and no encoding specified, just return
|
|
||||||
if isinstance(infile, basestring):
|
|
||||||
# infile read from a file will be a single string
|
|
||||||
return infile.splitlines(True)
|
|
||||||
return infile
|
|
||||||
|
|
||||||
|
if six.PY2 and isinstance(line, str):
|
||||||
def _a_to_u(self, aString):
|
# don't actually do any decoding, since we're on python 2 and
|
||||||
"""Decode ASCII strings to unicode if a self.encoding is specified."""
|
# returning a bytestring is fine
|
||||||
if self.encoding:
|
return self._decode(infile, None)
|
||||||
return aString.decode('ascii')
|
# No BOM discovered and no encoding specified, default to UTF-8
|
||||||
|
if isinstance(infile, six.binary_type):
|
||||||
|
return infile.decode('utf-8').splitlines(True)
|
||||||
else:
|
else:
|
||||||
return aString
|
return self._decode(infile, 'utf-8')
|
||||||
|
|
||||||
|
|
||||||
def _decode(self, infile, encoding):
|
def _decode(self, infile, encoding):
|
||||||
|
@ -1497,34 +1475,42 @@ class ConfigObj(Section):
|
||||||
|
|
||||||
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, basestring):
|
if isinstance(infile, six.binary_type):
|
||||||
# can't be unicode
|
|
||||||
# NOTE: Could raise a ``UnicodeDecodeError``
|
# NOTE: Could raise a ``UnicodeDecodeError``
|
||||||
return infile.decode(encoding).splitlines(True)
|
if encoding:
|
||||||
for i, line in enumerate(infile):
|
return infile.decode(encoding).splitlines(True)
|
||||||
if not isinstance(line, unicode):
|
else:
|
||||||
# NOTE: The isinstance test here handles mixed lists of unicode/string
|
return infile.splitlines(True)
|
||||||
# NOTE: But the decode will break on any non-string values
|
if isinstance(infile, six.string_types):
|
||||||
# NOTE: Or could raise a ``UnicodeDecodeError``
|
return infile.splitlines(True)
|
||||||
infile[i] = line.decode(encoding)
|
|
||||||
|
if encoding:
|
||||||
|
for i, line in enumerate(infile):
|
||||||
|
if isinstance(line, six.binary_type):
|
||||||
|
# NOTE: The isinstance test here handles mixed lists of unicode/string
|
||||||
|
# NOTE: But the decode will break on any non-string values
|
||||||
|
# NOTE: Or could raise a ``UnicodeDecodeError``
|
||||||
|
infile[i] = line.decode(encoding)
|
||||||
return infile
|
return infile
|
||||||
|
|
||||||
|
|
||||||
def _decode_element(self, line):
|
def _decode_element(self, line):
|
||||||
"""Decode element to unicode if necessary."""
|
"""Decode element to unicode if necessary."""
|
||||||
if not self.encoding:
|
if isinstance(line, six.binary_type) and self.default_encoding:
|
||||||
return line
|
|
||||||
if isinstance(line, str) and self.default_encoding:
|
|
||||||
return line.decode(self.default_encoding)
|
return line.decode(self.default_encoding)
|
||||||
return line
|
else:
|
||||||
|
return line
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: this may need to be modified
|
||||||
def _str(self, value):
|
def _str(self, value):
|
||||||
"""
|
"""
|
||||||
Used by ``stringify`` within validate, to turn non-string values
|
Used by ``stringify`` within validate, to turn non-string values
|
||||||
into strings.
|
into strings.
|
||||||
"""
|
"""
|
||||||
if not isinstance(value, basestring):
|
if not isinstance(value, six.string_types):
|
||||||
|
# intentially 'str' because it's just whatever the "normal"
|
||||||
|
# string type is for the python version we're dealing with
|
||||||
return str(value)
|
return str(value)
|
||||||
else:
|
else:
|
||||||
return value
|
return value
|
||||||
|
@ -1542,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:
|
||||||
|
@ -1550,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
|
||||||
|
@ -1571,7 +1558,7 @@ class ConfigObj(Section):
|
||||||
self.indent_type = indent
|
self.indent_type = indent
|
||||||
cur_depth = sect_open.count('[')
|
cur_depth = sect_open.count('[')
|
||||||
if cur_depth != sect_close.count(']'):
|
if cur_depth != sect_close.count(']'):
|
||||||
self._handle_error("Cannot compute the section depth at line %s.",
|
self._handle_error("Cannot compute the section depth",
|
||||||
NestingError, infile, cur_index)
|
NestingError, infile, cur_index)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -1581,7 +1568,7 @@ class ConfigObj(Section):
|
||||||
parent = self._match_depth(this_section,
|
parent = self._match_depth(this_section,
|
||||||
cur_depth).parent
|
cur_depth).parent
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
self._handle_error("Cannot compute nesting level at line %s.",
|
self._handle_error("Cannot compute nesting level",
|
||||||
NestingError, infile, cur_index)
|
NestingError, infile, cur_index)
|
||||||
continue
|
continue
|
||||||
elif cur_depth == this_section.depth:
|
elif cur_depth == this_section.depth:
|
||||||
|
@ -1591,12 +1578,13 @@ class ConfigObj(Section):
|
||||||
# the new section is a child the current section
|
# the new section is a child the current section
|
||||||
parent = this_section
|
parent = this_section
|
||||||
else:
|
else:
|
||||||
self._handle_error("Section too nested at line %s.",
|
self._handle_error("Section too nested",
|
||||||
NestingError, infile, cur_index)
|
NestingError, infile, cur_index)
|
||||||
|
continue
|
||||||
|
|
||||||
sect_name = self._unquote(sect_name)
|
sect_name = self._unquote(sect_name)
|
||||||
if sect_name in parent:
|
if sect_name in parent:
|
||||||
self._handle_error('Duplicate section name at line %s.',
|
self._handle_error('Duplicate section name',
|
||||||
DuplicateError, infile, cur_index)
|
DuplicateError, infile, cur_index)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -1615,10 +1603,8 @@ class ConfigObj(Section):
|
||||||
# so it should be a valid ``key = value`` line
|
# so it should be a valid ``key = value`` line
|
||||||
mat = self._keyword.match(line)
|
mat = self._keyword.match(line)
|
||||||
if mat is None:
|
if mat is None:
|
||||||
# it neither matched as a keyword
|
|
||||||
# or a section marker
|
|
||||||
self._handle_error(
|
self._handle_error(
|
||||||
'Invalid line at line "%s".',
|
'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
|
||||||
|
@ -1633,7 +1619,7 @@ class ConfigObj(Section):
|
||||||
value, infile, cur_index, maxline)
|
value, infile, cur_index, maxline)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
self._handle_error(
|
self._handle_error(
|
||||||
'Parse error in value at line %s.',
|
'Parse error in multiline value',
|
||||||
ParseError, infile, cur_index)
|
ParseError, infile, cur_index)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
|
@ -1641,26 +1627,24 @@ class ConfigObj(Section):
|
||||||
comment = ''
|
comment = ''
|
||||||
try:
|
try:
|
||||||
value = unrepr(value)
|
value = unrepr(value)
|
||||||
except Exception, e:
|
except Exception as cause:
|
||||||
if type(e) == UnknownType:
|
if isinstance(cause, UnknownType):
|
||||||
msg = 'Unknown name or type in value at line %s.'
|
msg = 'Unknown name or type in value'
|
||||||
else:
|
else:
|
||||||
msg = 'Parse error in value at line %s.'
|
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, e:
|
except Exception as cause:
|
||||||
if isinstance(e, UnknownType):
|
if isinstance(cause, UnknownType):
|
||||||
msg = 'Unknown name or type in value at line %s.'
|
msg = 'Unknown name or type in value'
|
||||||
else:
|
else:
|
||||||
msg = 'Parse error in value at line %s.'
|
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
|
||||||
|
@ -1668,14 +1652,14 @@ class ConfigObj(Section):
|
||||||
(value, comment) = self._handle_value(value)
|
(value, comment) = self._handle_value(value)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
self._handle_error(
|
self._handle_error(
|
||||||
'Parse error in value at line %s.',
|
'Parse error in value',
|
||||||
ParseError, infile, cur_index)
|
ParseError, infile, cur_index)
|
||||||
continue
|
continue
|
||||||
#
|
#
|
||||||
key = self._unquote(key)
|
key = self._unquote(key)
|
||||||
if key in this_section:
|
if key in this_section:
|
||||||
self._handle_error(
|
self._handle_error(
|
||||||
'Duplicate keyword name at line %s.',
|
'Duplicate keyword name',
|
||||||
DuplicateError, infile, cur_index)
|
DuplicateError, infile, cur_index)
|
||||||
continue
|
continue
|
||||||
# add the key.
|
# add the key.
|
||||||
|
@ -1726,7 +1710,7 @@ class ConfigObj(Section):
|
||||||
"""
|
"""
|
||||||
line = infile[cur_index]
|
line = infile[cur_index]
|
||||||
cur_index += 1
|
cur_index += 1
|
||||||
message = 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
|
||||||
|
@ -1777,8 +1761,10 @@ class ConfigObj(Section):
|
||||||
return self._quote(value[0], multiline=False) + ','
|
return self._quote(value[0], multiline=False) + ','
|
||||||
return ', '.join([self._quote(val, multiline=False)
|
return ', '.join([self._quote(val, multiline=False)
|
||||||
for val in value])
|
for val in value])
|
||||||
if not isinstance(value, basestring):
|
if not isinstance(value, six.string_types):
|
||||||
if self.stringify:
|
if self.stringify:
|
||||||
|
# intentially 'str' because it's just whatever the "normal"
|
||||||
|
# string type is for the python version we're dealing with
|
||||||
value = str(value)
|
value = str(value)
|
||||||
else:
|
else:
|
||||||
raise TypeError('Value "%s" is not a string.' % value)
|
raise TypeError('Value "%s" is not a string.' % value)
|
||||||
|
@ -1798,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)):
|
||||||
|
@ -1807,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)
|
||||||
|
@ -1817,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:
|
||||||
|
@ -1825,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,
|
||||||
|
@ -1929,12 +1905,12 @@ class ConfigObj(Section):
|
||||||
raise_errors=True,
|
raise_errors=True,
|
||||||
file_error=True,
|
file_error=True,
|
||||||
_inspec=True)
|
_inspec=True)
|
||||||
except ConfigObjError, 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, 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
|
||||||
|
|
||||||
|
@ -1977,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)
|
||||||
|
|
||||||
|
|
||||||
|
@ -2023,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
|
||||||
|
@ -2032,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
|
||||||
|
@ -2043,13 +2024,13 @@ 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])
|
||||||
|
|
||||||
if isinstance(this_entry, dict):
|
if isinstance(this_entry, Section):
|
||||||
# a section
|
# a section
|
||||||
out.append(self._write_marker(
|
out.append(self._write_marker(
|
||||||
indent_string,
|
indent_string,
|
||||||
|
@ -2068,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
|
||||||
|
|
||||||
|
@ -2096,22 +2077,26 @@ 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 self.encoding:
|
|
||||||
output = output.encode(self.encoding)
|
|
||||||
if self.BOM and ((self.encoding is None) or match_utf8(self.encoding)):
|
|
||||||
# Add the UTF8 BOM
|
|
||||||
output = BOM_UTF8 + output
|
|
||||||
|
|
||||||
if not output.endswith(newline):
|
if not output.endswith(newline):
|
||||||
output += newline
|
output += newline
|
||||||
if outfile is not None:
|
|
||||||
outfile.write(output)
|
|
||||||
else:
|
|
||||||
h = open(self.filename, 'wb')
|
|
||||||
h.write(output)
|
|
||||||
h.close()
|
|
||||||
|
|
||||||
|
if isinstance(output, six.binary_type):
|
||||||
|
output_bytes = output
|
||||||
|
else:
|
||||||
|
output_bytes = output.encode(self.encoding or
|
||||||
|
self.default_encoding or
|
||||||
|
'ascii')
|
||||||
|
|
||||||
|
if self.BOM and ((self.encoding is None) or match_utf8(self.encoding)):
|
||||||
|
# Add the UTF8 BOM
|
||||||
|
output_bytes = BOM_UTF8 + output_bytes
|
||||||
|
|
||||||
|
if outfile is not None:
|
||||||
|
outfile.write(output_bytes)
|
||||||
|
else:
|
||||||
|
with open(self.filename, 'wb') as h:
|
||||||
|
h.write(output_bytes)
|
||||||
|
|
||||||
def validate(self, validator, preserve_errors=False, copy=False,
|
def validate(self, validator, preserve_errors=False, copy=False,
|
||||||
section=None):
|
section=None):
|
||||||
|
@ -2155,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
|
||||||
|
@ -2189,12 +2174,12 @@ class ConfigObj(Section):
|
||||||
val,
|
val,
|
||||||
missing=missing
|
missing=missing
|
||||||
)
|
)
|
||||||
except validator.baseErrorClass, 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:
|
||||||
|
@ -2338,7 +2323,7 @@ class ConfigObj(Section):
|
||||||
This method raises a ``ReloadError`` if the ConfigObj doesn't have
|
This method raises a ``ReloadError`` if the ConfigObj doesn't have
|
||||||
a filename attribute pointing to a file.
|
a filename attribute pointing to a file.
|
||||||
"""
|
"""
|
||||||
if not isinstance(self.filename, basestring):
|
if not isinstance(self.filename, six.string_types):
|
||||||
raise ReloadError()
|
raise ReloadError()
|
||||||
|
|
||||||
filename = self.filename
|
filename = self.filename
|
||||||
|
@ -2416,16 +2401,16 @@ def flatten_errors(cfg, res, levels=None, results=None):
|
||||||
levels = []
|
levels = []
|
||||||
results = []
|
results = []
|
||||||
if res == True:
|
if res == True:
|
||||||
return results
|
return sorted(results)
|
||||||
if res == False or isinstance(res, Exception):
|
if res == False or isinstance(res, Exception):
|
||||||
results.append((levels[:], None, res))
|
results.append((levels[:], None, res))
|
||||||
if levels:
|
if levels:
|
||||||
levels.pop()
|
levels.pop()
|
||||||
return results
|
return sorted(results)
|
||||||
for (key, val) in res.items():
|
for (key, val) in list(res.items()):
|
||||||
if val == True:
|
if val == True:
|
||||||
continue
|
continue
|
||||||
if isinstance(cfg.get(key), dict):
|
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)
|
||||||
|
@ -2436,7 +2421,7 @@ def flatten_errors(cfg, res, levels=None, results=None):
|
||||||
if levels:
|
if levels:
|
||||||
levels.pop()
|
levels.pop()
|
||||||
#
|
#
|
||||||
return results
|
return sorted(results)
|
||||||
|
|
||||||
|
|
||||||
def get_extra_values(conf, _prepend=()):
|
def get_extra_values(conf, _prepend=()):
|
2
lib/configobj/_version.py
Normal file
2
lib/configobj/_version.py
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
"""Project version"""
|
||||||
|
__version__ = '5.1.0'
|
1474
lib/configobj/validate.py
Normal file
1474
lib/configobj/validate.py
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue