diff options
Diffstat (limited to 'src/tox/config/loader/str_convert.py')
-rw-r--r-- | src/tox/config/loader/str_convert.py | 36 |
1 files changed, 34 insertions, 2 deletions
diff --git a/src/tox/config/loader/str_convert.py b/src/tox/config/loader/str_convert.py index f07545f5..ad7ffa5d 100644 --- a/src/tox/config/loader/str_convert.py +++ b/src/tox/config/loader/str_convert.py @@ -46,10 +46,42 @@ class StrConvert(Convert[str]): raise TypeError(f"dictionary lines must be of form key=value, found {row!r}") @staticmethod + def _win32_process_path_backslash(value: str, escape: str, special_chars: str) -> str: + """ + Escape backslash in value that is not followed by a special character. + + This allows windows paths to be written without double backslash, while + retaining the POSIX backslash escape semantics for quotes and escapes. + """ + result = [] + for ix, char in enumerate(value): + result.append(char) + if char == escape: + last_char = value[ix - 1 : ix] + if last_char == escape: + continue + next_char = value[ix + 1 : ix + 2] + if next_char not in (escape, *special_chars): + result.append(escape) # escape escapes that are not themselves escaping a special character + return "".join(result) + + @staticmethod def to_command(value: str) -> Command: - is_win = sys.platform == "win32" + """ + At this point, ``value`` has already been substituted out, and all punctuation / escapes are final. + + Value will typically be stripped of whitespace when coming from an ini file. + """ value = value.replace(r"\#", "#") - splitter = shlex.shlex(value, posix=not is_win) + is_win = sys.platform == "win32" + if is_win: # pragma: win32 cover + s = shlex.shlex(posix=True) + value = StrConvert._win32_process_path_backslash( + value, + escape=s.escape, + special_chars=s.quotes + s.whitespace, + ) + splitter = shlex.shlex(value, posix=True) splitter.whitespace_split = True splitter.commenters = "" # comments handled earlier, and the shlex does not know escaped comment characters args: list[str] = [] |