Commit 75b53392 authored by Julien Muchembled's avatar Julien Muchembled Committed by Xavier Thompson

[feat] Add support for escaping $ with $$

parent 1b199067
...@@ -58,6 +58,7 @@ import sys ...@@ -58,6 +58,7 @@ import sys
import tempfile import tempfile
import zc.buildout import zc.buildout
import zc.buildout.download import zc.buildout.download
from functools import partial
PY3 = sys.version_info[0] == 3 PY3 = sys.version_info[0] == 3
if PY3: if PY3:
...@@ -833,7 +834,7 @@ class Buildout(DictMixin): ...@@ -833,7 +834,7 @@ class Buildout(DictMixin):
if part in install_parts: if part in install_parts:
old_options = installed_part_options[part].copy() old_options = installed_part_options[part].copy()
installed_files = old_options.pop('__buildout_installed__') installed_files = old_options.pop('__buildout_installed__')
new_options = self.get(part) new_options = self.get(part).copy()
if old_options == new_options: if old_options == new_options:
# The options are the same, but are all of the # The options are the same, but are all of the
# installed files still there? If not, we should # installed files still there? If not, we should
...@@ -1575,11 +1576,15 @@ class Options(DictMixin): ...@@ -1575,11 +1576,15 @@ class Options(DictMixin):
def _dosub(self, option, v): def _dosub(self, option, v):
__doing__ = 'Getting option %s:%s.', self.name, option __doing__ = 'Getting option %s:%s.', self.name, option
seen = [(self.name, option)] seen = [(self.name, option)]
v = '$$'.join([self._sub(s, seen) for s in v.split('$$')]) v = '$$'.join([self._sub(s, seen, last=False)
for s in v.split('$$')])
self._cooked[option] = v self._cooked[option] = v
def get(self, option, default=None, seen=None): def get(self, option, default=None, seen=None, last=True):
try: try:
if last:
return self._data[option].replace('$${', '${')
else:
return self._data[option] return self._data[option]
except KeyError: except KeyError:
pass pass
...@@ -1602,16 +1607,20 @@ class Options(DictMixin): ...@@ -1602,16 +1607,20 @@ class Options(DictMixin):
) )
else: else:
seen.append(key) seen.append(key)
v = '$$'.join([self._sub(s, seen) for s in v.split('$$')]) v = '$$'.join([self._sub(s, seen, last=False)
for s in v.split('$$')])
seen.pop() seen.pop()
self._data[option] = v self._data[option] = v
if last:
return v.replace('$${', '${')
else:
return v return v
_template_split = re.compile('([$]{[^}]*})').split _template_split = re.compile('([$]{[^}]*})').split
_simple = re.compile('[-a-zA-Z0-9 ._]+$').match _simple = re.compile('[-a-zA-Z0-9 ._]+$').match
_valid = re.compile(r'\${[-a-zA-Z0-9 ._]*:[-a-zA-Z0-9 ._]+}$').match _valid = re.compile(r'\${[-a-zA-Z0-9 ._]*:[-a-zA-Z0-9 ._]+}$').match
def _sub(self, template, seen): def _sub(self, template, seen, last=True):
value = self._template_split(template) value = self._template_split(template)
subs = [] subs = []
for ref in value[1::2]: for ref in value[1::2]:
...@@ -1639,7 +1648,7 @@ class Options(DictMixin): ...@@ -1639,7 +1648,7 @@ class Options(DictMixin):
section, option = s section, option = s
if not section: if not section:
section = self.name section = self.name
v = self.buildout[section].get(option, None, seen) v = self.buildout[section].get(option, None, seen, last=last)
if v is None: if v is None:
if option == '_buildout_section_name_': if option == '_buildout_section_name_':
v = self.name v = self.name
...@@ -1652,11 +1661,6 @@ class Options(DictMixin): ...@@ -1652,11 +1661,6 @@ class Options(DictMixin):
return ''.join([''.join(v) for v in zip(value[::2], subs)]) return ''.join([''.join(v) for v in zip(value[::2], subs)])
def __getitem__(self, key): def __getitem__(self, key):
try:
return self._data[key]
except KeyError:
pass
v = self.get(key) v = self.get(key)
if v is None: if v is None:
raise MissingOption("Missing option: %s:%s" % (self.name, key)) raise MissingOption("Missing option: %s:%s" % (self.name, key))
...@@ -1768,10 +1772,12 @@ def _save_option(option, value, f): ...@@ -1768,10 +1772,12 @@ def _save_option(option, value, f):
def _save_options(section, options, f): def _save_options(section, options, f):
print_('[%s]' % section, file=f) print_('[%s]' % section, file=f)
items = list(options.items()) if isinstance(options, Options):
items.sort() get_option = partial(options.get, last=False)
for option, value in items: else:
_save_option(option, value, f) get_option = options.get
for option in sorted(options):
_save_option(option, get_option(option), f)
def _default_globals(): def _default_globals():
"""Return a mapping of default and precomputed expressions. """Return a mapping of default and precomputed expressions.
......
...@@ -1181,6 +1181,8 @@ Uninstall recipes need to be called when a part is removed too: ...@@ -1181,6 +1181,8 @@ Uninstall recipes need to be called when a part is removed too:
uninstalling uninstalling
Installing demo. Installing demo.
installing installing
Section `demo` contains unused option(s): 'x'.
This may be an indication for either a typo in the option's name or a bug in the used recipe.
>>> write('buildout.cfg', ''' >>> write('buildout.cfg', '''
...@@ -2924,6 +2926,73 @@ def increment_on_command_line(): ...@@ -2924,6 +2926,73 @@ def increment_on_command_line():
recipe='zc.buildout:debug' recipe='zc.buildout:debug'
""" """
def bug_664539_simple_buildout():
r"""
>>> write('buildout.cfg', '''
... [buildout]
... parts = escape
...
... [escape]
... recipe = zc.buildout:debug
... foo = $${nonexistent:option}
... ''')
>>> print_(system(buildout), end='')
Installing escape.
foo='${nonexistent:option}'
recipe='zc.buildout:debug'
"""
def bug_664539_reference():
r"""
>>> write('buildout.cfg', '''
... [buildout]
... parts = escape
...
... [escape]
... recipe = zc.buildout:debug
... foo = ${:bar}
... bar = $${nonexistent:option}
... ''')
>>> print_(system(buildout), end='')
Installing escape.
bar='${nonexistent:option}'
foo='${nonexistent:option}'
recipe='zc.buildout:debug'
"""
def bug_664539_complex_buildout():
r"""
>>> write('buildout.cfg', '''
... [buildout]
... parts = escape
...
... [escape]
... recipe = zc.buildout:debug
... foo = ${level1:foo}
...
... [level1]
... recipe = zc.buildout:debug
... foo = ${level2:foo}
...
... [level2]
... recipe = zc.buildout:debug
... foo = $${nonexistent:option}
... ''')
>>> print_(system(buildout), end='')
Installing level2.
foo='${nonexistent:option}'
recipe='zc.buildout:debug'
Installing level1.
foo='${nonexistent:option}'
recipe='zc.buildout:debug'
Installing escape.
foo='${nonexistent:option}'
recipe='zc.buildout:debug'
"""
def test_constrained_requirement(): def test_constrained_requirement():
""" """
zc.buildout.easy_install._constrained_requirement(constraint, requirement) zc.buildout.easy_install._constrained_requirement(constraint, requirement)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment