migrate to Python3 (from alejeune)

This commit is contained in:
htouvet
2022-04-25 10:02:43 +02:00
parent 1655977e64
commit bc4b9811ed
65 changed files with 574 additions and 3884 deletions
+25
View File
@@ -0,0 +1,25 @@
# Copyright (c) 2001, 2002, 2003 Python Software Foundation
# Copyright (c) 2004-2008 Paramjit Oberoi <param.cs.wisc.edu>
# Copyright (c) 2007 Tim Lauridsen <tla@rasmil.dk>
# All Rights Reserved. See LICENSE-PSF & LICENSE for details.
from .ini import INIConfig, change_comment_syntax
from .config import BasicConfig, ConfigNamespace
from .compat import RawConfigParser, ConfigParser, SafeConfigParser
from .utils import tidy
from .configparser import DuplicateSectionError, \
NoSectionError, NoOptionError, \
InterpolationMissingOptionError, \
InterpolationDepthError, \
InterpolationSyntaxError, \
DEFAULTSECT, MAX_INTERPOLATION_DEPTH
__all__ = [
'BasicConfig', 'ConfigNamespace',
'INIConfig', 'tidy', 'change_comment_syntax',
'RawConfigParser', 'ConfigParser', 'SafeConfigParser',
'DuplicateSectionError', 'NoSectionError', 'NoOptionError',
'InterpolationMissingOptionError', 'InterpolationDepthError',
'InterpolationSyntaxError', 'DEFAULTSECT', 'MAX_INTERPOLATION_DEPTH',
]
+343
View File
@@ -0,0 +1,343 @@
# Copyright (c) 2001, 2002, 2003 Python Software Foundation
# Copyright (c) 2004-2008 Paramjit Oberoi <param.cs.wisc.edu>
# All Rights Reserved. See LICENSE-PSF & LICENSE for details.
"""Compatibility interfaces for ConfigParser
Interfaces of ConfigParser, RawConfigParser and SafeConfigParser
should be completely identical to the Python standard library
versions. Tested with the unit tests included with Python-2.3.4
The underlying INIConfig object can be accessed as cfg.data
"""
import re
from .configparser import DuplicateSectionError, \
NoSectionError, NoOptionError, \
InterpolationMissingOptionError, \
InterpolationDepthError, \
InterpolationSyntaxError, \
DEFAULTSECT, MAX_INTERPOLATION_DEPTH
# These are imported only for compatiability.
# The code below does not reference them directly.
from .configparser import Error, InterpolationError, \
MissingSectionHeaderError, ParsingError
import six
from . import ini
class RawConfigParser(object):
def __init__(self, defaults=None, dict_type=dict):
if dict_type != dict:
raise ValueError('Custom dict types not supported')
self.data = ini.INIConfig(defaults=defaults, optionxformsource=self)
def optionxform(self, optionstr):
return optionstr.lower()
def defaults(self):
d = {}
secobj = self.data._defaults
for name in secobj._options:
d[name] = secobj._compat_get(name)
return d
def sections(self):
"""Return a list of section names, excluding [DEFAULT]"""
return list(self.data)
def add_section(self, section):
"""Create a new section in the configuration.
Raise DuplicateSectionError if a section by the specified name
already exists. Raise ValueError if name is DEFAULT or any of
its case-insensitive variants.
"""
# The default section is the only one that gets the case-insensitive
# treatment - so it is special-cased here.
if section.lower() == "default":
raise ValueError('Invalid section name: %s' % section)
if self.has_section(section):
raise DuplicateSectionError(section)
else:
self.data._new_namespace(section)
def has_section(self, section):
"""Indicate whether the named section is present in the configuration.
The DEFAULT section is not acknowledged.
"""
return section in self.data
def options(self, section):
"""Return a list of option names for the given section name."""
if section in self.data:
return list(self.data[section])
else:
raise NoSectionError(section)
def read(self, filenames):
"""Read and parse a filename or a list of filenames.
Files that cannot be opened are silently ignored; this is
designed so that you can specify a list of potential
configuration file locations (e.g. current directory, user's
home directory, systemwide directory), and all existing
configuration files in the list will be read. A single
filename may also be given.
"""
files_read = []
if isinstance(filenames, six.string_types):
filenames = [filenames]
for filename in filenames:
try:
fp = open(filename)
except IOError:
continue
files_read.append(filename)
self.data._readfp(fp)
fp.close()
return files_read
def readfp(self, fp, filename=None):
"""Like read() but the argument must be a file-like object.
The `fp' argument must have a `readline' method. Optional
second argument is the `filename', which if not given, is
taken from fp.name. If fp has no `name' attribute, `<???>' is
used.
"""
self.data._readfp(fp)
def get(self, section, option, vars=None):
if not self.has_section(section):
raise NoSectionError(section)
sec = self.data[section]
if option in sec:
return sec._compat_get(option)
else:
raise NoOptionError(option, section)
def items(self, section):
if section in self.data:
ans = []
for opt in self.data[section]:
ans.append((opt, self.get(section, opt)))
return ans
else:
raise NoSectionError(section)
def getint(self, section, option):
return int(self.get(section, option))
def getfloat(self, section, option):
return float(self.get(section, option))
_boolean_states = {'1': True, 'yes': True, 'true': True, 'on': True,
'0': False, 'no': False, 'false': False, 'off': False}
def getboolean(self, section, option):
v = self.get(section, option)
if v.lower() not in self._boolean_states:
raise ValueError('Not a boolean: %s' % v)
return self._boolean_states[v.lower()]
def has_option(self, section, option):
"""Check for the existence of a given option in a given section."""
if section in self.data:
sec = self.data[section]
else:
raise NoSectionError(section)
return (option in sec)
def set(self, section, option, value):
"""Set an option."""
if section in self.data:
self.data[section][option] = value
else:
raise NoSectionError(section)
def write(self, fp):
"""Write an .ini-format representation of the configuration state."""
fp.write(str(self.data))
def remove_option(self, section, option):
"""Remove an option."""
if section in self.data:
sec = self.data[section]
else:
raise NoSectionError(section)
if option in sec:
del sec[option]
return 1
else:
return 0
def remove_section(self, section):
"""Remove a file section."""
if not self.has_section(section):
return False
del self.data[section]
return True
class ConfigDict(object):
"""Present a dict interface to a ini section."""
def __init__(self, cfg, section, vars):
self.cfg = cfg
self.section = section
self.vars = vars
def __getitem__(self, key):
try:
return RawConfigParser.get(self.cfg, self.section, key, self.vars)
except (NoOptionError, NoSectionError):
raise KeyError(key)
class ConfigParser(RawConfigParser):
def get(self, section, option, raw=False, vars=None):
"""Get an option value for a given section.
All % interpolations are expanded in the return values, based on the
defaults passed into the constructor, unless the optional argument
`raw' is true. Additional substitutions may be provided using the
`vars' argument, which must be a dictionary whose contents overrides
any pre-existing defaults.
The section DEFAULT is special.
"""
if section != DEFAULTSECT and not self.has_section(section):
raise NoSectionError(section)
option = self.optionxform(option)
value = RawConfigParser.get(self, section, option, vars)
if raw:
return value
else:
d = ConfigDict(self, section, vars)
return self._interpolate(section, option, value, d)
def _interpolate(self, section, option, rawval, vars):
# do the string interpolation
value = rawval
depth = MAX_INTERPOLATION_DEPTH
while depth: # Loop through this until it's done
depth -= 1
if "%(" in value:
try:
value = value % vars
except KeyError as e:
raise InterpolationMissingOptionError(
option, section, rawval, e.args[0])
else:
break
if value.find("%(") != -1:
raise InterpolationDepthError(option, section, rawval)
return value
def items(self, section, raw=False, vars=None):
"""Return a list of tuples with (name, value) for each option
in the section.
All % interpolations are expanded in the return values, based on the
defaults passed into the constructor, unless the optional argument
`raw' is true. Additional substitutions may be provided using the
`vars' argument, which must be a dictionary whose contents overrides
any pre-existing defaults.
The section DEFAULT is special.
"""
if section != DEFAULTSECT and not self.has_section(section):
raise NoSectionError(section)
if vars is None:
options = list(self.data[section])
else:
options = []
for x in self.data[section]:
if x not in vars:
options.append(x)
options.extend(vars.keys())
if "__name__" in options:
options.remove("__name__")
d = ConfigDict(self, section, vars)
if raw:
return [(option, d[option])
for option in options]
else:
return [(option, self._interpolate(section, option, d[option], d))
for option in options]
class SafeConfigParser(ConfigParser):
_interpvar_re = re.compile(r"%\(([^)]+)\)s")
_badpercent_re = re.compile(r"%[^%]|%$")
def set(self, section, option, value):
if not isinstance(value, six.string_types):
raise TypeError("option values must be strings")
# check for bad percent signs:
# first, replace all "good" interpolations
tmp_value = self._interpvar_re.sub('', value)
# then, check if there's a lone percent sign left
m = self._badpercent_re.search(tmp_value)
if m:
raise ValueError("invalid interpolation syntax in %r at "
"position %d" % (value, m.start()))
ConfigParser.set(self, section, option, value)
def _interpolate(self, section, option, rawval, vars):
# do the string interpolation
L = []
self._interpolate_some(option, L, rawval, section, vars, 1)
return ''.join(L)
_interpvar_match = re.compile(r"%\(([^)]+)\)s").match
def _interpolate_some(self, option, accum, rest, section, map, depth):
if depth > MAX_INTERPOLATION_DEPTH:
raise InterpolationDepthError(option, section, rest)
while rest:
p = rest.find("%")
if p < 0:
accum.append(rest)
return
if p > 0:
accum.append(rest[:p])
rest = rest[p:]
# p is no longer used
c = rest[1:2]
if c == "%":
accum.append("%")
rest = rest[2:]
elif c == "(":
m = self._interpvar_match(rest)
if m is None:
raise InterpolationSyntaxError(option, section, "bad interpolation variable reference %r" % rest)
var = m.group(1)
rest = rest[m.end():]
try:
v = map[var]
except KeyError:
raise InterpolationMissingOptionError(
option, section, rest, var)
if "%" in v:
self._interpolate_some(option, accum, v,
section, map, depth + 1)
else:
accum.append(v)
else:
raise InterpolationSyntaxError(
option, section,
"'%' must be followed by '%' or '(', found: " + repr(rest))
+292
View File
@@ -0,0 +1,292 @@
class ConfigNamespace(object):
"""Abstract class representing the interface of Config objects.
A ConfigNamespace is a collection of names mapped to values, where
the values may be nested namespaces. Values can be accessed via
container notation - obj[key] - or via dotted notation - obj.key.
Both these access methods are equivalent.
To minimize name conflicts between namespace keys and class members,
the number of class members should be minimized, and the names of
all class members should start with an underscore.
Subclasses must implement the methods for container-like access,
and this class will automatically provide dotted access.
"""
# Methods that must be implemented by subclasses
def _getitem(self, key):
return NotImplementedError(key)
def __setitem__(self, key, value):
raise NotImplementedError(key, value)
def __delitem__(self, key):
raise NotImplementedError(key)
def __iter__(self):
return NotImplementedError()
def _new_namespace(self, name):
raise NotImplementedError(name)
def __contains__(self, key):
try:
self._getitem(key)
except KeyError:
return False
return True
# Machinery for converting dotted access into container access,
# and automatically creating new sections/namespaces.
#
# To distinguish between accesses of class members and namespace
# keys, we first call object.__getattribute__(). If that succeeds,
# the name is assumed to be a class member. Otherwise it is
# treated as a namespace key.
#
# Therefore, member variables should be defined in the class,
# not just in the __init__() function. See BasicNamespace for
# an example.
def __getitem__(self, key):
try:
return self._getitem(key)
except KeyError:
return Undefined(key, self)
def __getattr__(self, name):
try:
return self._getitem(name)
except KeyError:
if name.startswith('__') and name.endswith('__'):
raise AttributeError
return Undefined(name, self)
def __setattr__(self, name, value):
try:
object.__getattribute__(self, name)
object.__setattr__(self, name, value)
except AttributeError:
self.__setitem__(name, value)
def __delattr__(self, name):
try:
object.__getattribute__(self, name)
object.__delattr__(self, name)
except AttributeError:
self.__delitem__(name)
# During unpickling, Python checks if the class has a __setstate__
# method. But, the data dicts have not been initialised yet, which
# leads to _getitem and hence __getattr__ raising an exception. So
# we explicitly impement default __setstate__ behavior.
def __setstate__(self, state):
self.__dict__.update(state)
class Undefined(object):
"""Helper class used to hold undefined names until assignment.
This class helps create any undefined subsections when an
assignment is made to a nested value. For example, if the
statement is "cfg.a.b.c = 42", but "cfg.a.b" does not exist yet.
"""
def __init__(self, name, namespace):
object.__setattr__(self, 'name', name)
object.__setattr__(self, 'namespace', namespace)
def __setattr__(self, name, value):
obj = self.namespace._new_namespace(self.name)
obj[name] = value
def __setitem__(self, name, value):
obj = self.namespace._new_namespace(self.name)
obj[name] = value
# ---- Basic implementation of a ConfigNamespace
class BasicConfig(ConfigNamespace):
"""Represents a hierarchical collection of named values.
Values are added using dotted notation:
>>> n = BasicConfig()
>>> n.x = 7
>>> n.name.first = 'paramjit'
>>> n.name.last = 'oberoi'
...and accessed the same way, or with [...]:
>>> n.x
7
>>> n.name.first
'paramjit'
>>> n.name.last
'oberoi'
>>> n['x']
7
>>> n['name']['first']
'paramjit'
Iterating over the namespace object returns the keys:
>>> l = list(n)
>>> l.sort()
>>> l
['name', 'x']
Values can be deleted using 'del' and printed using 'print'.
>>> n.aaa = 42
>>> del n.x
>>> print(n)
aaa = 42
name.first = paramjit
name.last = oberoi
Nested namespaces are also namespaces:
>>> isinstance(n.name, ConfigNamespace)
True
>>> print(n.name)
first = paramjit
last = oberoi
>>> sorted(list(n.name))
['first', 'last']
Finally, values can be read from a file as follows:
>>> from six import StringIO
>>> sio = StringIO('''
... # comment
... ui.height = 100
... ui.width = 150
... complexity = medium
... have_python
... data.secret.password = goodness=gracious me
... ''')
>>> n = BasicConfig()
>>> n._readfp(sio)
>>> print(n)
complexity = medium
data.secret.password = goodness=gracious me
have_python
ui.height = 100
ui.width = 150
"""
# this makes sure that __setattr__ knows this is not a namespace key
_data = None
def __init__(self):
self._data = {}
def _getitem(self, key):
return self._data[key]
def __setitem__(self, key, value):
self._data[key] = value
def __delitem__(self, key):
del self._data[key]
def __iter__(self):
return iter(self._data)
def __str__(self, prefix=''):
lines = []
keys = list(self._data.keys())
keys.sort()
for name in keys:
value = self._data[name]
if isinstance(value, ConfigNamespace):
lines.append(value.__str__(prefix='%s%s.' % (prefix,name)))
else:
if value is None:
lines.append('%s%s' % (prefix, name))
else:
lines.append('%s%s = %s' % (prefix, name, value))
return '\n'.join(lines)
def _new_namespace(self, name):
obj = BasicConfig()
self._data[name] = obj
return obj
def _readfp(self, fp):
while True:
line = fp.readline()
if not line:
break
line = line.strip()
if not line: continue
if line[0] == '#': continue
data = line.split('=', 1)
if len(data) == 1:
name = line
value = None
else:
name = data[0].strip()
value = data[1].strip()
name_components = name.split('.')
ns = self
for n in name_components[:-1]:
if n in ns:
ns = ns[n]
if not isinstance(ns, ConfigNamespace):
raise TypeError('value-namespace conflict', n)
else:
ns = ns._new_namespace(n)
ns[name_components[-1]] = value
# ---- Utility functions
def update_config(target, source):
"""Imports values from source into target.
Recursively walks the <source> ConfigNamespace and inserts values
into the <target> ConfigNamespace. For example:
>>> n = BasicConfig()
>>> n.playlist.expand_playlist = True
>>> n.ui.display_clock = True
>>> n.ui.display_qlength = True
>>> n.ui.width = 150
>>> print(n)
playlist.expand_playlist = True
ui.display_clock = True
ui.display_qlength = True
ui.width = 150
>>> from iniparse import ini
>>> i = ini.INIConfig()
>>> update_config(i, n)
>>> print(i)
[playlist]
expand_playlist = True
<BLANKLINE>
[ui]
display_clock = True
display_qlength = True
width = 150
"""
for name in sorted(source):
value = source[name]
if isinstance(value, ConfigNamespace):
if name in target:
myns = target[name]
if not isinstance(myns, ConfigNamespace):
raise TypeError('value-namespace conflict')
else:
myns = target._new_namespace(name)
update_config(myns, value)
else:
target[name] = value
+7
View File
@@ -0,0 +1,7 @@
try:
from ConfigParser import *
# not all objects get imported with __all__
from ConfigParser import Error, InterpolationMissingOptionError
except ImportError:
from configparser import *
from configparser import Error, InterpolationMissingOptionError
+652
View File
@@ -0,0 +1,652 @@
"""Access and/or modify INI files
* Compatiable with ConfigParser
* Preserves order of sections & options
* Preserves comments/blank lines/etc
* More conveninet access to data
Example:
>>> from six import StringIO
>>> sio = StringIO('''# configure foo-application
... [foo]
... bar1 = qualia
... bar2 = 1977
... [foo-ext]
... special = 1''')
>>> cfg = INIConfig(sio)
>>> print(cfg.foo.bar1)
qualia
>>> print(cfg['foo-ext'].special)
1
>>> cfg.foo.newopt = 'hi!'
>>> cfg.baz.enabled = 0
>>> print(cfg)
# configure foo-application
[foo]
bar1 = qualia
bar2 = 1977
newopt = hi!
[foo-ext]
special = 1
<BLANKLINE>
[baz]
enabled = 0
"""
# An ini parser that supports ordered sections/options
# Also supports updates, while preserving structure
# Backward-compatiable with ConfigParser
import re
from .configparser import DEFAULTSECT, ParsingError, MissingSectionHeaderError
import six
from . import config
class LineType(object):
line = None
def __init__(self, line=None):
if line is not None:
self.line = line.strip('\n')
# Return the original line for unmodified objects
# Otherwise construct using the current attribute values
def __str__(self):
if self.line is not None:
return self.line
else:
return self.to_string()
# If an attribute is modified after initialization
# set line to None since it is no longer accurate.
def __setattr__(self, name, value):
if hasattr(self,name):
self.__dict__['line'] = None
self.__dict__[name] = value
def to_string(self):
raise Exception('This method must be overridden in derived classes')
class SectionLine(LineType):
regex = re.compile(r'^\['
r'(?P<name>[^]]+)'
r'\]\s*'
r'((?P<csep>;|#)(?P<comment>.*))?$')
def __init__(self, name, comment=None, comment_separator=None,
comment_offset=-1, line=None):
super(SectionLine, self).__init__(line)
self.name = name
self.comment = comment
self.comment_separator = comment_separator
self.comment_offset = comment_offset
def to_string(self):
out = '[' + self.name + ']'
if self.comment is not None:
# try to preserve indentation of comments
out = (out+' ').ljust(self.comment_offset)
out = out + self.comment_separator + self.comment
return out
def parse(cls, line):
m = cls.regex.match(line.rstrip())
if m is None:
return None
return cls(m.group('name'), m.group('comment'),
m.group('csep'), m.start('csep'),
line)
parse = classmethod(parse)
class OptionLine(LineType):
def __init__(self, name, value, separator=' = ', comment=None,
comment_separator=None, comment_offset=-1, line=None):
super(OptionLine, self).__init__(line)
self.name = name
self.value = value
self.separator = separator
self.comment = comment
self.comment_separator = comment_separator
self.comment_offset = comment_offset
def to_string(self):
out = '%s%s%s' % (self.name, self.separator, self.value)
if self.comment is not None:
# try to preserve indentation of comments
out = (out+' ').ljust(self.comment_offset)
out = out + self.comment_separator + self.comment
return out
regex = re.compile(r'^(?P<name>[^:=\s[][^:=]*)'
r'(?P<sep>[:=]\s*)'
r'(?P<value>.*)$')
def parse(cls, line):
m = cls.regex.match(line.rstrip())
if m is None:
return None
name = m.group('name').rstrip()
value = m.group('value')
sep = m.group('name')[len(name):] + m.group('sep')
# comments are not detected in the regex because
# ensuring total compatibility with ConfigParser
# requires that:
# option = value ;comment // value=='value'
# option = value;1 ;comment // value=='value;1 ;comment'
#
# Doing this in a regex would be complicated. I
# think this is a bug. The whole issue of how to
# include ';' in the value needs to be addressed.
# Also, '#' doesn't mark comments in options...
coff = value.find(';')
if coff != -1 and value[coff-1].isspace():
comment = value[coff+1:]
csep = value[coff]
value = value[:coff].rstrip()
coff = m.start('value') + coff
else:
comment = None
csep = None
coff = -1
return cls(name, value, sep, comment, csep, coff, line)
parse = classmethod(parse)
def change_comment_syntax(comment_chars='%;#', allow_rem=False):
comment_chars = re.sub(r'([\]\-\^])', r'\\\1', comment_chars)
regex = r'^(?P<csep>[%s]' % comment_chars
if allow_rem:
regex += '|[rR][eE][mM]'
regex += r')(?P<comment>.*)$'
CommentLine.regex = re.compile(regex)
class CommentLine(LineType):
regex = re.compile(r'^(?P<csep>[;#])'
r'(?P<comment>.*)$')
def __init__(self, comment='', separator='#', line=None):
super(CommentLine, self).__init__(line)
self.comment = comment
self.separator = separator
def to_string(self):
return self.separator + self.comment
def parse(cls, line):
m = cls.regex.match(line.rstrip())
if m is None:
return None
return cls(m.group('comment'), m.group('csep'), line)
parse = classmethod(parse)
class EmptyLine(LineType):
# could make this a singleton
def to_string(self):
return ''
value = property(lambda self: '')
def parse(cls, line):
if line.strip():
return None
return cls(line)
parse = classmethod(parse)
class ContinuationLine(LineType):
regex = re.compile(r'^\s+(?P<value>.*)$')
def __init__(self, value, value_offset=None, line=None):
super(ContinuationLine, self).__init__(line)
self.value = value
if value_offset is None:
value_offset = 8
self.value_offset = value_offset
def to_string(self):
return ' '*self.value_offset + self.value
def parse(cls, line):
m = cls.regex.match(line.rstrip())
if m is None:
return None
return cls(m.group('value'), m.start('value'), line)
parse = classmethod(parse)
class LineContainer(object):
def __init__(self, d=None):
self.contents = []
self.orgvalue = None
if d:
if isinstance(d, list): self.extend(d)
else: self.add(d)
def add(self, x):
self.contents.append(x)
def extend(self, x):
for i in x: self.add(i)
def get_name(self):
return self.contents[0].name
def set_name(self, data):
self.contents[0].name = data
def get_value(self):
if self.orgvalue is not None:
return self.orgvalue
elif len(self.contents) == 1:
return self.contents[0].value
else:
return '\n'.join([('%s' % x.value) for x in self.contents
if not isinstance(x, CommentLine)])
def set_value(self, data):
self.orgvalue = data
lines = ('%s' % data).split('\n')
# If there is an existing ContinuationLine, use its offset
value_offset = None
for v in self.contents:
if isinstance(v, ContinuationLine):
value_offset = v.value_offset
break
# Rebuild contents list, preserving initial OptionLine
self.contents = self.contents[0:1]
self.contents[0].value = lines[0]
del lines[0]
for line in lines:
if line.strip():
self.add(ContinuationLine(line, value_offset))
else:
self.add(EmptyLine())
name = property(get_name, set_name)
value = property(get_value, set_value)
def __str__(self):
s = [x.__str__() for x in self.contents]
return '\n'.join(s)
def finditer(self, key):
for x in self.contents[::-1]:
if hasattr(x, 'name') and x.name==key:
yield x
def find(self, key):
for x in self.finditer(key):
return x
raise KeyError(key)
def _make_xform_property(myattrname, srcattrname=None):
private_attrname = myattrname + 'value'
private_srcname = myattrname + 'source'
if srcattrname is None:
srcattrname = myattrname
def getfn(self):
srcobj = getattr(self, private_srcname)
if srcobj is not None:
return getattr(srcobj, srcattrname)
else:
return getattr(self, private_attrname)
def setfn(self, value):
srcobj = getattr(self, private_srcname)
if srcobj is not None:
setattr(srcobj, srcattrname, value)
else:
setattr(self, private_attrname, value)
return property(getfn, setfn)
class INISection(config.ConfigNamespace):
_lines = None
_options = None
_defaults = None
_optionxformvalue = None
_optionxformsource = None
_compat_skip_empty_lines = set()
def __init__(self, lineobj, defaults=None, optionxformvalue=None, optionxformsource=None):
self._lines = [lineobj]
self._defaults = defaults
self._optionxformvalue = optionxformvalue
self._optionxformsource = optionxformsource
self._options = {}
_optionxform = _make_xform_property('_optionxform')
def _compat_get(self, key):
# identical to __getitem__ except that _compat_XXX
# is checked for backward-compatible handling
if key == '__name__':
return self._lines[-1].name
if self._optionxform: key = self._optionxform(key)
try:
value = self._options[key].value
del_empty = key in self._compat_skip_empty_lines
except KeyError:
if self._defaults and key in self._defaults._options:
value = self._defaults._options[key].value
del_empty = key in self._defaults._compat_skip_empty_lines
else:
raise
if del_empty:
value = re.sub('\n+', '\n', value)
return value
def _getitem(self, key):
if key == '__name__':
return self._lines[-1].name
if self._optionxform: key = self._optionxform(key)
try:
return self._options[key].value
except KeyError:
if self._defaults and key in self._defaults._options:
return self._defaults._options[key].value
else:
raise
def __setitem__(self, key, value):
if self._optionxform: xkey = self._optionxform(key)
else: xkey = key
if xkey in self._compat_skip_empty_lines:
self._compat_skip_empty_lines.remove(xkey)
if xkey not in self._options:
# create a dummy object - value may have multiple lines
obj = LineContainer(OptionLine(key, ''))
self._lines[-1].add(obj)
self._options[xkey] = obj
# the set_value() function in LineContainer
# automatically handles multi-line values
self._options[xkey].value = value
def __delitem__(self, key):
if self._optionxform: key = self._optionxform(key)
if key in self._compat_skip_empty_lines:
self._compat_skip_empty_lines.remove(key)
for l in self._lines:
remaining = []
for o in l.contents:
if isinstance(o, LineContainer):
n = o.name
if self._optionxform: n = self._optionxform(n)
if key != n: remaining.append(o)
else:
remaining.append(o)
l.contents = remaining
del self._options[key]
def __iter__(self):
d = set()
for l in self._lines:
for x in l.contents:
if isinstance(x, LineContainer):
if self._optionxform:
ans = self._optionxform(x.name)
else:
ans = x.name
if ans not in d:
yield ans
d.add(ans)
if self._defaults:
for x in self._defaults:
if x not in d:
yield x
d.add(x)
def _new_namespace(self, name):
raise Exception('No sub-sections allowed', name)
def make_comment(line):
return CommentLine(line.rstrip('\n'))
def readline_iterator(f):
"""iterate over a file by only using the file object's readline method"""
have_newline = False
while True:
line = f.readline()
if not line:
if have_newline:
yield ""
return
if line.endswith('\n'):
have_newline = True
else:
have_newline = False
yield line
def lower(x):
return x.lower()
class INIConfig(config.ConfigNamespace):
_data = None
_sections = None
_defaults = None
_optionxformvalue = None
_optionxformsource = None
_sectionxformvalue = None
_sectionxformsource = None
_parse_exc = None
_bom = False
def __init__(self, fp=None, defaults=None, parse_exc=True,
optionxformvalue=lower, optionxformsource=None,
sectionxformvalue=None, sectionxformsource=None):
self._data = LineContainer()
self._parse_exc = parse_exc
self._optionxformvalue = optionxformvalue
self._optionxformsource = optionxformsource
self._sectionxformvalue = sectionxformvalue
self._sectionxformsource = sectionxformsource
self._sections = {}
if defaults is None: defaults = {}
self._defaults = INISection(LineContainer(), optionxformsource=self)
for name, value in defaults.items():
self._defaults[name] = value
if fp is not None:
self._readfp(fp)
_optionxform = _make_xform_property('_optionxform', 'optionxform')
_sectionxform = _make_xform_property('_sectionxform', 'optionxform')
def _getitem(self, key):
if key == DEFAULTSECT:
return self._defaults
if self._sectionxform: key = self._sectionxform(key)
return self._sections[key]
def __setitem__(self, key, value):
raise Exception('Values must be inside sections', key, value)
def __delitem__(self, key):
if self._sectionxform: key = self._sectionxform(key)
for line in self._sections[key]._lines:
self._data.contents.remove(line)
del self._sections[key]
def __iter__(self):
d = set()
d.add(DEFAULTSECT)
for x in self._data.contents:
if isinstance(x, LineContainer):
if x.name not in d:
yield x.name
d.add(x.name)
def _new_namespace(self, name):
if self._data.contents:
self._data.add(EmptyLine())
obj = LineContainer(SectionLine(name))
self._data.add(obj)
if self._sectionxform: name = self._sectionxform(name)
if name in self._sections:
ns = self._sections[name]
ns._lines.append(obj)
else:
ns = INISection(obj, defaults=self._defaults,
optionxformsource=self)
self._sections[name] = ns
return ns
def __str__(self):
if self._bom:
fmt = u'\ufeff%s'
else:
fmt = '%s'
return fmt % self._data.__str__()
__unicode__ = __str__
_line_types = [EmptyLine, CommentLine,
SectionLine, OptionLine,
ContinuationLine]
def _parse(self, line):
for linetype in self._line_types:
lineobj = linetype.parse(line)
if lineobj:
return lineobj
else:
# can't parse line
return None
def _readfp(self, fp):
cur_section = None
cur_option = None
cur_section_name = None
cur_option_name = None
pending_lines = []
pending_empty_lines = False
try:
fname = fp.name
except AttributeError:
fname = '<???>'
line_count = 0
exc = None
line = None
for line in readline_iterator(fp):
# Check for BOM on first line
if line_count == 0 and isinstance(line, six.text_type):
if line[0] == u'\ufeff':
line = line[1:]
self._bom = True
line_obj = self._parse(line)
line_count += 1
if not cur_section and not isinstance(line_obj, (CommentLine, EmptyLine, SectionLine)):
if self._parse_exc:
raise MissingSectionHeaderError(fname, line_count, line)
else:
line_obj = make_comment(line)
if line_obj is None:
if self._parse_exc:
if exc is None:
exc = ParsingError(fname)
exc.append(line_count, line)
line_obj = make_comment(line)
if isinstance(line_obj, ContinuationLine):
if cur_option:
if pending_lines:
cur_option.extend(pending_lines)
pending_lines = []
if pending_empty_lines:
optobj._compat_skip_empty_lines.add(cur_option_name)
pending_empty_lines = False
cur_option.add(line_obj)
else:
# illegal continuation line - convert to comment
if self._parse_exc:
if exc is None:
exc = ParsingError(fname)
exc.append(line_count, line)
line_obj = make_comment(line)
if isinstance(line_obj, OptionLine):
if pending_lines:
cur_section.extend(pending_lines)
pending_lines = []
pending_empty_lines = False
cur_option = LineContainer(line_obj)
cur_section.add(cur_option)
if self._optionxform:
cur_option_name = self._optionxform(cur_option.name)
else:
cur_option_name = cur_option.name
if cur_section_name == DEFAULTSECT:
optobj = self._defaults
else:
optobj = self._sections[cur_section_name]
optobj._options[cur_option_name] = cur_option
if isinstance(line_obj, SectionLine):
self._data.extend(pending_lines)
pending_lines = []
pending_empty_lines = False
cur_section = LineContainer(line_obj)
self._data.add(cur_section)
cur_option = None
cur_option_name = None
if cur_section.name == DEFAULTSECT:
self._defaults._lines.append(cur_section)
cur_section_name = DEFAULTSECT
else:
if self._sectionxform:
cur_section_name = self._sectionxform(cur_section.name)
else:
cur_section_name = cur_section.name
if cur_section_name not in self._sections:
self._sections[cur_section_name] = \
INISection(cur_section, defaults=self._defaults,
optionxformsource=self)
else:
self._sections[cur_section_name]._lines.append(cur_section)
if isinstance(line_obj, (CommentLine, EmptyLine)):
pending_lines.append(line_obj)
if isinstance(line_obj, EmptyLine):
pending_empty_lines = True
self._data.extend(pending_lines)
if line and line[-1] == '\n':
self._data.add(EmptyLine())
if exc:
raise exc
+48
View File
@@ -0,0 +1,48 @@
from . import compat
from .ini import LineContainer, EmptyLine
def tidy(cfg):
"""Clean up blank lines.
This functions makes the configuration look clean and
handwritten - consecutive empty lines and empty lines at
the start of the file are removed, and one is guaranteed
to be at the end of the file.
"""
if isinstance(cfg, compat.RawConfigParser):
cfg = cfg.data
cont = cfg._data.contents
i = 1
while i < len(cont):
if isinstance(cont[i], LineContainer):
tidy_section(cont[i])
i += 1
elif (isinstance(cont[i-1], EmptyLine) and
isinstance(cont[i], EmptyLine)):
del cont[i]
else:
i += 1
# Remove empty first line
if cont and isinstance(cont[0], EmptyLine):
del cont[0]
# Ensure a last line
if cont and not isinstance(cont[-1], EmptyLine):
cont.append(EmptyLine())
def tidy_section(lc):
cont = lc.contents
i = 1
while i < len(cont):
if isinstance(cont[i-1], EmptyLine) and isinstance(cont[i], EmptyLine):
del cont[i]
else:
i += 1
# Remove empty first line
if len(cont) > 1 and isinstance(cont[1], EmptyLine):
del cont[1]