Skip to content

Commit 1120fb0

Browse files
committed
Quote paths containing #
1 parent e4d0d60 commit 1120fb0

File tree

1 file changed

+31
-12
lines changed

1 file changed

+31
-12
lines changed

src/tox/config/__init__.py

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import pluggy
1919
import py
2020
import toml
21+
from py._path.common import PathBase
2122
from packaging import requirements
2223
from packaging.utils import canonicalize_name
2324

@@ -390,7 +391,7 @@ def get(self, name, default=None):
390391
return os.environ.get(name, default)
391392
self._lookupstack.append(name)
392393
try:
393-
res = self.reader._replace(val)
394+
res = self.reader._replace(val, unquote_path=False)
394395
res = res.replace("\\{", "{").replace("\\}", "}")
395396
self.resolved[name] = res
396397
finally:
@@ -1591,7 +1592,7 @@ def addsubstitutions(self, _posargs=None, **kw):
15911592
self.posargs = _posargs
15921593

15931594
def getpath(self, name, defaultpath, replace=True):
1594-
path = self.getstring(name, defaultpath, replace=replace)
1595+
path = self.getstring(name, defaultpath, replace=replace, unquote_path=True)
15951596
if path is not None:
15961597
toxinidir = self._subs["toxinidir"]
15971598
return toxinidir.join(path, abs=True)
@@ -1680,7 +1681,15 @@ def getargv_install_command(self, name, default="", replace=True):
16801681

16811682
return _ArgvlistReader.getargvlist(self, s, replace=replace)[0]
16821683

1683-
def getstring(self, name, default=None, replace=True, crossonly=False, no_fallback=False):
1684+
def getstring(
1685+
self,
1686+
name,
1687+
default=None,
1688+
replace=True,
1689+
crossonly=False,
1690+
no_fallback=False,
1691+
unquote_path=False,
1692+
):
16841693
x = None
16851694
sections = [self.section_name] + ([] if no_fallback else self.fallbacksections)
16861695
for s in sections:
@@ -1698,10 +1707,10 @@ def getstring(self, name, default=None, replace=True, crossonly=False, no_fallba
16981707
# process. Once they are unwrapped, we call apply factors
16991708
# again for those new dependencies.
17001709
x = self._apply_factors(x)
1701-
x = self._replace_if_needed(x, name, replace, crossonly)
1710+
x = self._replace_if_needed(x, name, replace, crossonly, unquote_path=unquote_path)
17021711
x = self._apply_factors(x)
17031712

1704-
x = self._replace_if_needed(x, name, replace, crossonly)
1713+
x = self._replace_if_needed(x, name, replace, crossonly, unquote_path=unquote_path)
17051714
return x
17061715

17071716
def getposargs(self, default=None):
@@ -1715,9 +1724,9 @@ def getposargs(self, default=None):
17151724
else:
17161725
return default or ""
17171726

1718-
def _replace_if_needed(self, x, name, replace, crossonly):
1727+
def _replace_if_needed(self, x, name, replace, crossonly, unquote_path=False):
17191728
if replace and x and hasattr(x, "replace"):
1720-
x = self._replace(x, name=name, crossonly=crossonly)
1729+
x = self._replace(x, name=name, crossonly=crossonly, unquote_path=unquote_path)
17211730
return x
17221731

17231732
def _apply_factors(self, s):
@@ -1736,14 +1745,17 @@ def factor_line(line):
17361745
lines = s.strip().splitlines()
17371746
return "\n".join(filter(None, map(factor_line, lines)))
17381747

1739-
def _replace(self, value, name=None, section_name=None, crossonly=False):
1748+
def _replace(self, value, name=None, section_name=None, crossonly=False, unquote_path=False):
17401749
if "{" not in value:
17411750
return value
17421751

17431752
section_name = section_name if section_name else self.section_name
17441753
self._subststack.append((section_name, name))
17451754
try:
1746-
replaced = Replacer(self, crossonly=crossonly).do_replace(value)
1755+
replacer = Replacer(self, crossonly=crossonly)
1756+
replaced = replacer.do_replace(value)
1757+
if unquote_path and replacer._path_quoted:
1758+
replaced = replaced.strip("'")
17471759
assert self._subststack.pop() == (section_name, name)
17481760
except tox.exception.MissingSubstitution:
17491761
if not section_name.startswith(testenvprefix):
@@ -1770,6 +1782,7 @@ class Replacer:
17701782
def __init__(self, reader, crossonly=False):
17711783
self.reader = reader
17721784
self.crossonly = crossonly
1785+
self._path_quoted = False
17731786

17741787
def do_replace(self, value):
17751788
"""
@@ -1853,6 +1866,7 @@ def _substitute_from_other_section(self, key):
18531866
name=item,
18541867
section_name=section,
18551868
crossonly=self.crossonly,
1869+
unquote_path=False,
18561870
)
18571871

18581872
raise tox.exception.ConfigError("substitution key {!r} not found".format(key))
@@ -1864,6 +1878,11 @@ def _replace_substitution(self, match):
18641878
val = self._substitute_from_other_section(sub_key)
18651879
if callable(val):
18661880
val = val()
1881+
if isinstance(val, PathBase):
1882+
val = str(val)
1883+
if "#" in val:
1884+
val = "'{}'".format(val)
1885+
self._path_quoted = True
18671886
return str(val)
18681887

18691888

@@ -1895,7 +1914,7 @@ def getargvlist(cls, reader, value, replace=True):
18951914
current_command += line
18961915

18971916
if is_section_substitution(current_command):
1898-
replaced = reader._replace(current_command, crossonly=True)
1917+
replaced = reader._replace(current_command, crossonly=True, unquote_path=False)
18991918
commands.extend(cls.getargvlist(reader, replaced))
19001919
else:
19011920
commands.append(cls.processcommand(reader, current_command, replace))
@@ -1924,8 +1943,8 @@ def processcommand(cls, reader, command, replace=True):
19241943
continue
19251944

19261945
new_arg = ""
1927-
new_word = reader._replace(word)
1928-
new_word = reader._replace(new_word)
1946+
new_word = reader._replace(word, unquote_path=False)
1947+
new_word = reader._replace(new_word, unquote_path=False)
19291948
new_word = new_word.replace("\\{", "{").replace("\\}", "}")
19301949
new_arg += new_word
19311950
newcommand += new_arg

0 commit comments

Comments
 (0)