Skip to content

Commit 24a61d9

Browse files
committed
Suport # character in path of tox project
Signed-off-by: Bernát Gábor <[email protected]>
1 parent 174fd08 commit 24a61d9

File tree

6 files changed

+40
-6
lines changed

6 files changed

+40
-6
lines changed

docs/changelog/763.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Support ``#`` character in path for the tox project - by :user:`gaborbernat`.

src/tox/config/loader/ini/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,17 @@ def __init__(self, section: str, parser: ConfigParser, overrides: List[Override]
2929
super().__init__(overrides)
3030

3131
def load_raw(self, key: str, conf: Optional["Config"], env_name: Optional[str]) -> str:
32-
value = self._section[key]
32+
return self.process_raw(conf, env_name, self._section[key])
3333

34+
@staticmethod
35+
def process_raw(conf: Optional["Config"], env_name: Optional[str], value: str) -> str:
3436
# strip comments
3537
elements: List[str] = []
3638
for line in value.split("\n"):
3739
if not line.startswith("#"):
3840
part = _COMMENTS.sub("", line)
3941
elements.append(part.replace("\\#", "#"))
4042
strip_comments = "\n".join(elements)
41-
4243
if conf is None: # conf is None when we're loading the global tox configuration file for the CLI
4344
factor_filtered = strip_comments # we don't support factor and replace functionality there
4445
else:

src/tox/config/loader/ini/replace.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
# split alongside :, unless it's escaped, or it's preceded by a single capital letter (Windows drive letter in paths)
2222
ARGS_GROUP = re.compile(r"(?<!\\\\|:[A-Z]):")
23+
NOT_ESCAPED_COMMENT = re.compile(r"(?<!\\)#")
2324

2425

2526
def replace(conf: "Config", name: Optional[str], loader: "IniLoader", value: str, chain: List[str]) -> str:
@@ -120,9 +121,11 @@ def replace_reference(
120121
for src in _config_value_sources(settings["env"], settings["section"], current_env, conf, loader):
121122
try:
122123
if isinstance(src, SectionProxy):
123-
return src[key]
124+
return loader.process_raw(conf, current_env, src[key])
124125
value = src.load(key, chain)
125126
as_str, _ = stringify(value)
127+
# escape unescaped comment characters to preserve them during processing
128+
as_str = NOT_ESCAPED_COMMENT.sub("\\#", as_str)
126129
return as_str
127130
except KeyError as exc: # if fails, keep trying maybe another source can satisfy
128131
exception = exc

src/tox/config/set_env.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def load(self, item: str, chain: Optional[List[str]] = None) -> str:
4141
return self._materialized[item]
4242
raw = self._raw[item]
4343
result = self.replacer(raw, chain) # apply any replace options
44+
result = result.replace(r"\#", "#") # unroll escaped comment with replacement
4445
self._materialized[item] = result
4546
self._raw.pop(item, None) # if the replace requires the env we may be called again, so allow pop to fail
4647
return result

src/tox/pytest.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -370,17 +370,19 @@ def matches(pattern: str, text: str, flags: int = 0) -> None:
370370

371371

372372
class ToxProjectCreator(Protocol):
373-
def __call__(self, files: Dict[str, Any], base: Optional[Path] = None) -> ToxProject: # noqa: U100
373+
def __call__(
374+
self, files: Dict[str, Any], base: Optional[Path] = None, prj_path: Optional[Path] = None # noqa: U100
375+
) -> ToxProject:
374376
...
375377

376378

377379
@pytest.fixture(name="tox_project")
378380
def init_fixture(
379381
tmp_path: Path, capfd: CaptureFixture, monkeypatch: MonkeyPatch, mocker: MockerFixture
380382
) -> ToxProjectCreator:
381-
def _init(files: Dict[str, Any], base: Optional[Path] = None) -> ToxProject:
383+
def _init(files: Dict[str, Any], base: Optional[Path] = None, prj_path: Optional[Path] = None) -> ToxProject:
382384
"""create tox projects"""
383-
return ToxProject(files, base, tmp_path / "p", capfd, monkeypatch, mocker)
385+
return ToxProject(files, base, prj_path or tmp_path / "p", capfd, monkeypatch, mocker)
384386

385387
return _init # noqa
386388

tests/session/cmd/test_show_config.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import platform
22
import sys
33
from configparser import ConfigParser
4+
from pathlib import Path
5+
from textwrap import dedent
46
from typing import Callable, Tuple
57

68
import pytest
@@ -155,3 +157,27 @@ def test_show_config_description_normalize(tox_project: ToxProjectCreator) -> No
155157
outcome = tox_project({"tox.ini": tox_ini}).run("c", "-e", "py", "-k", "description")
156158
outcome.assert_success()
157159
assert outcome.out == "[testenv:py]\ndescription = A magical pipe of this\n"
160+
161+
162+
def test_show_config_ini_comment_path(tox_project: ToxProjectCreator, tmp_path: Path) -> None:
163+
prj_path = tmp_path / "#magic"
164+
prj_path.mkdir()
165+
ini = """
166+
[testenv]
167+
package = skip
168+
set_env =
169+
A=1 # comment
170+
# more comment
171+
commands = {envpython} -c 'import os; print(os.linesep.join(f"{k}={v!r}" for k, v in os.environ.items()))'
172+
[testenv:py]
173+
set_env =
174+
{[testenv]set_env}
175+
B = {tox_root} # just some comment
176+
"""
177+
project = tox_project({"tox.ini": dedent(ini)}, prj_path=prj_path)
178+
result = project.run("r", "-e", "py")
179+
result.assert_success()
180+
a_line = next(i for i in result.out.splitlines() if i.startswith("A=")) # pragma: no branch # not found raises
181+
assert a_line == "A='1'"
182+
b_line = next(i for i in result.out.splitlines() if i.startswith("B=")) # pragma: no branch # not found raises
183+
assert b_line == f"B='{prj_path}'"

0 commit comments

Comments
 (0)