Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow {posargs} in setenv #1697

Merged
merged 2 commits into from
Oct 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTORS
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Jake Windle
Jannis Leidel
Joachim Brandon LeBlanc
Johannes Christ
John Mark Vandenberg
Jon Dufresne
Josh Smeaton
Josh Snyder
Expand Down
1 change: 1 addition & 0 deletions docs/changelog/1695.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow {posargs} in setenv. - by :user:`jayvdb`
62 changes: 40 additions & 22 deletions src/tox/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,8 @@ def postprocess(self, testenv_config, value):

class InstallcmdOption:
name = "install_command"
type = "argv"
default = "python -m pip install {opts} {packages}"
type = "argv_install_command"
default = r"python -m pip install \{opts\} \{packages\}"
help = "install command for dependencies and package under test."

def postprocess(self, testenv_config, value):
Expand Down Expand Up @@ -1374,6 +1374,7 @@ def make_envconfig(self, name, section, subs, config, replace=True):
"dict_setenv",
"argv",
"argvlist",
"argv_install_command",
):
meth = getattr(reader, "get{}".format(atype))
res = meth(env_attr.name, env_attr.default, replace=replace)
Expand Down Expand Up @@ -1558,7 +1559,15 @@ def __repr__(self):


class SectionReader:
def __init__(self, section_name, cfgparser, fallbacksections=None, factors=(), prefix=None):
def __init__(
self,
section_name,
cfgparser,
fallbacksections=None,
factors=(),
prefix=None,
posargs="",
):
if prefix is None:
self.section_name = section_name
else:
Expand All @@ -1569,6 +1578,7 @@ def __init__(self, section_name, cfgparser, fallbacksections=None, factors=(), p
self._subs = {}
self._subststack = []
self._setenv = None
self.posargs = posargs

def get_environ_value(self, name):
if self._setenv is None:
Expand Down Expand Up @@ -1661,6 +1671,15 @@ def getargvlist(self, name, default="", replace=True):
def getargv(self, name, default="", replace=True):
return self.getargvlist(name, default, replace=replace)[0]

def getargv_install_command(self, name, default="", replace=True):
s = self.getstring(name, default, replace=False)
if "{packages}" in s:
s = s.replace("{packages}", r"\{packages\}")
if "{opts}" in s:
s = s.replace("{opts}", r"\{opts\}")

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

def getstring(self, name, default=None, replace=True, crossonly=False, no_fallback=False):
x = None
sections = [self.section_name] + ([] if no_fallback else self.fallbacksections)
Expand All @@ -1685,6 +1704,17 @@ def getstring(self, name, default=None, replace=True, crossonly=False, no_fallba
x = self._replace_if_needed(x, name, replace, crossonly)
return x

def getposargs(self, default=None):
if self.posargs:
posargs = self.posargs
if sys.platform.startswith("win"):
posargs_string = list2cmdline([x for x in posargs if x])
else:
posargs_string = " ".join([shlex_quote(x) for x in posargs if x])
return posargs_string
else:
return default or ""

def _replace_if_needed(self, x, name, replace, crossonly):
if replace and x and hasattr(x, "replace"):
x = self._replace(x, name=name, crossonly=crossonly)
Expand Down Expand Up @@ -1771,11 +1801,8 @@ def _replace_match(self, match):
if not any(g.values()):
return os.pathsep

# special case: opts and packages. Leave {opts} and
# {packages} intact, they are replaced manually in
# _venv.VirtualEnv.run_install_command.
if sub_value in ("opts", "packages"):
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This removal caused #1777

return "{{{}}}".format(sub_value)
if sub_value == "posargs":
return self.reader.getposargs(match.group("default_value"))

try:
sub_type = g["sub_type"]
Expand All @@ -1790,6 +1817,8 @@ def _replace_match(self, match):
if is_interactive():
return match.group("substitution_value")
return match.group("default_value")
if sub_type == "posargs":
return self.reader.getposargs(match.group("substitution_value"))
if sub_type is not None:
raise tox.exception.ConfigError(
"No support for the {} substitution type".format(sub_type),
Expand Down Expand Up @@ -1883,28 +1912,17 @@ def getargvlist(cls, reader, value, replace=True):

@classmethod
def processcommand(cls, reader, command, replace=True):
posargs = getattr(reader, "posargs", "")
if sys.platform.startswith("win"):
posargs_string = list2cmdline([x for x in posargs if x])
else:
posargs_string = " ".join([shlex_quote(x) for x in posargs if x])

# Iterate through each word of the command substituting as
# appropriate to construct the new command string. This
# string is then broken up into exec argv components using
# shlex.
if replace:
newcommand = ""
for word in CommandParser(command).words():
if word == "{posargs}" or word == "[]":
newcommand += posargs_string
if word == "[]":
newcommand += reader.getposargs()
continue
elif word.startswith("{posargs:") and word.endswith("}"):
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This case wasn't handled correctly in the new code, if : was in the middle of this text range, resulting in regression #1785

if posargs:
newcommand += posargs_string
continue
else:
word = word[9:-1]

new_arg = ""
new_word = reader._replace(word)
new_word = reader._replace(new_word)
Expand Down
15 changes: 15 additions & 0 deletions tests/unit/config/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,21 @@ def test_command_env_substitution(self, newconfig):
assert envconfig.commands == [["ls", "testvalue"]]
assert envconfig.setenv["TEST"] == "testvalue"

def test_command_env_substitution_posargs(self, newconfig):
"""Ensure {posargs} values are substituted correctly."""
config = newconfig(
"""
[testenv:py27]
setenv =
TEST={posargs:default}
commands =
ls {env:TEST}
""",
)
envconfig = config.envconfigs["py27"]
assert envconfig.commands == [["ls", "default"]]
assert envconfig.setenv["TEST"] == "default"

def test_command_env_substitution_global(self, newconfig):
"""Ensure referenced {env:key:default} values are substituted correctly."""
config = newconfig(
Expand Down