115 lines
4.4 KiB
Python
115 lines
4.4 KiB
Python
|
import os
|
||
|
import os.path
|
||
|
import re
|
||
|
from configparser import Interpolation, NoSectionError, NoOptionError, InterpolationMissingOptionError, \
|
||
|
InterpolationDepthError, InterpolationSyntaxError, ConfigParser
|
||
|
|
||
|
MAX_INTERPOLATION_DEPTH = 10
|
||
|
|
||
|
|
||
|
class ExtendedInterpolatorWithEnv(Interpolation):
|
||
|
"""Advanced variant of interpolation, supports the syntax used by
|
||
|
`zc.buildout'. Enables interpolation between sections.
|
||
|
|
||
|
This modified version also allows specifying environment variables
|
||
|
using ${env:...}, and allows adding additional options using 'set_additional_options'. """
|
||
|
|
||
|
_KEYCRE = re.compile(r"\$\{([^}]+)\}")
|
||
|
|
||
|
def __init__(self, **kwargs):
|
||
|
self.__kwargs = kwargs
|
||
|
|
||
|
def set_additional_options(self, **kwargs):
|
||
|
self.__kwargs = kwargs
|
||
|
|
||
|
def before_get(self, parser, section, option, value, defaults):
|
||
|
L = []
|
||
|
self._interpolate_some(parser, option, L, value, section, defaults, 1)
|
||
|
return ''.join(L)
|
||
|
|
||
|
def before_set(self, parser, section, option, value):
|
||
|
tmp_value = value.replace('$$', '') # escaped dollar signs
|
||
|
tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax
|
||
|
if '$' in tmp_value:
|
||
|
raise ValueError("invalid interpolation syntax in %r at "
|
||
|
"position %d" % (value, tmp_value.find('$')))
|
||
|
return value
|
||
|
|
||
|
def _resolve_option(self, option, defaults):
|
||
|
if option in self.__kwargs:
|
||
|
return self.__kwargs[option]
|
||
|
return defaults[option]
|
||
|
|
||
|
def _resolve_section_option(self, section, option, parser):
|
||
|
if section == 'env':
|
||
|
return os.getenv(option, '')
|
||
|
return parser.get(section, option, raw=True)
|
||
|
|
||
|
def _interpolate_some(self, parser, option, accum, rest, section, map,
|
||
|
depth):
|
||
|
rawval = parser.get(section, option, raw=True, fallback=rest)
|
||
|
if depth > MAX_INTERPOLATION_DEPTH:
|
||
|
raise InterpolationDepthError(option, section, rawval)
|
||
|
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._KEYCRE.match(rest)
|
||
|
if m is None:
|
||
|
raise InterpolationSyntaxError(option, section,
|
||
|
"bad interpolation variable reference %r" % rest)
|
||
|
path = m.group(1).split(':')
|
||
|
rest = rest[m.end():]
|
||
|
sect = section
|
||
|
opt = option
|
||
|
try:
|
||
|
if len(path) == 1:
|
||
|
opt = parser.optionxform(path[0])
|
||
|
v = self._resolve_option(opt, map)
|
||
|
elif len(path) == 2:
|
||
|
sect = path[0]
|
||
|
opt = parser.optionxform(path[1])
|
||
|
v = self._resolve_section_option(sect, opt, parser)
|
||
|
else:
|
||
|
raise InterpolationSyntaxError(
|
||
|
option, section,
|
||
|
"More than one ':' found: %r" % (rest,))
|
||
|
except (KeyError, NoSectionError, NoOptionError):
|
||
|
raise InterpolationMissingOptionError(
|
||
|
option, section, rawval, ":".join(path)) from None
|
||
|
if "$" in v:
|
||
|
self._interpolate_some(parser, opt, accum, v, sect,
|
||
|
dict(parser.items(sect, raw=True)),
|
||
|
depth + 1)
|
||
|
else:
|
||
|
accum.append(v)
|
||
|
else:
|
||
|
raise InterpolationSyntaxError(
|
||
|
option, section,
|
||
|
"'$' must be followed by '$' or '{', "
|
||
|
"found: %r" % (rest,))
|
||
|
|
||
|
|
||
|
class ConfigParserWithEnv(ConfigParser):
|
||
|
_DEFAULT_INTERPOLATION = ExtendedInterpolatorWithEnv()
|
||
|
|
||
|
def set_additional_interpolation_options(self, **kwargs):
|
||
|
"""
|
||
|
Sets additional options to be used in interpolation.
|
||
|
Only works with ExtendedInterpolatorWithEnv
|
||
|
:param kwargs:
|
||
|
:return:
|
||
|
"""
|
||
|
if isinstance(super()._interpolation, ExtendedInterpolatorWithEnv):
|
||
|
super()._interpolation.set_additional_options(**kwargs)
|