diff --git a/src/tox/config/__init__.py b/src/tox/config/__init__.py index 1b392cf53..319c93aea 100644 --- a/src/tox/config/__init__.py +++ b/src/tox/config/__init__.py @@ -1944,8 +1944,11 @@ def processcommand(cls, reader, command, replace=True): continue new_arg = "" + has_dual_quote = "''" in word new_word = reader._replace(word, unquote_path=False) new_word = reader._replace(new_word, unquote_path=False) + if not has_dual_quote: + new_word = new_word.replace("''", "'") new_word = new_word.replace("\\{", "{").replace("\\}", "}") new_arg += new_word newcommand += new_arg @@ -1980,8 +1983,14 @@ def word_has_ended(): and ps.word and ps.word[-1] not in string.whitespace ) - or (cur_char == "{" and ps.depth == 0 and not ps.word.endswith("\\")) - or (ps.depth == 0 and ps.word and ps.word[-1] == "}") + or ( + cur_char == "{" + and ps.depth == 0 + and not ps.word.endswith("\\") + and ps.word != "'" + ) + or (ps.depth == 0 and ps.word and ps.word[-1] == "}" and peek() != "'") + or (ps.depth == 0 and ps.word and ps.word[-2:] == "}'") or (cur_char not in string.whitespace and ps.word and ps.word.strip() == "") ) @@ -1995,6 +2004,12 @@ def yield_if_word_ended(): if word_has_ended(): yield_this_word() + def peek(): + try: + return self.command[i + 1] + except IndexError: + return "" + def accumulate(): ps.word += cur_char @@ -2004,7 +2019,7 @@ def push_substitution(): def pop_substitution(): ps.depth -= 1 - for cur_char in self.command: + for i, cur_char in enumerate(self.command): if cur_char in string.whitespace: if ps.depth == 0: yield_if_word_ended() @@ -2016,6 +2031,12 @@ def pop_substitution(): elif cur_char == "}": accumulate() pop_substitution() + elif cur_char == "'": + if ps.depth == 0 and ps.word and ps.word[-1] == "}": + accumulate() + else: + yield_if_word_ended() + accumulate() else: yield_if_word_ended() accumulate() diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py index 3b1b86422..445649886 100644 --- a/tests/unit/config/test_config.py +++ b/tests/unit/config/test_config.py @@ -526,7 +526,7 @@ def test_command_substitution_pound(self, tmpdir, newconfig): toxworkdir = {toxinidir}/.tox#dir [testenv:py27] - commands = {envpython} {toxworkdir} + commands = '{envpython}' '{toxworkdir}'/'foo#bar' """, ) @@ -541,7 +541,7 @@ def test_command_substitution_pound(self, tmpdir, newconfig): assert envconfig.commands[0] == [ str(envconfig.envbindir.join("python")), - str(config.toxworkdir.realpath()), + str(config.toxworkdir.realpath().join("foo#bar")), ] def test_command_substitution_whitespace(self, tmpdir, newconfig): @@ -552,7 +552,7 @@ def test_command_substitution_whitespace(self, tmpdir, newconfig): toxworkdir = {toxinidir}/.tox dir [testenv:py27] - commands = {envpython} {toxworkdir} + commands = '{envpython}' '{toxworkdir}'/'foo bar' """, ) @@ -567,7 +567,7 @@ def test_command_substitution_whitespace(self, tmpdir, newconfig): assert envconfig.commands[0] == [ str(envconfig.envbindir.join("python")), - str(config.toxworkdir.realpath()), + str(config.toxworkdir.realpath().join("foo bar")), ]