Skip to content

Commit f5c0d32

Browse files
committed
Fix shell quoting
1 parent 758ae23 commit f5c0d32

File tree

10 files changed

+136
-87
lines changed

10 files changed

+136
-87
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ project's virtual environment.
269269
shell will be used
270270
- Only schema version 2.0.0 is supported
271271
- If no `cwd` is specified, the current working directory is used for the task instead
272+
- Does not support deprecated options (`isShellCommand`, `isBuildCommand`)
272273
- Does not support any extensions that add extra options/functionality
273274
- Does not load any VS Code settings
274275
- Extra arguments option

tests/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from pytest_mock import MockerFixture
77

88
import vscode_task_runner.constants
9+
import vscode_task_runner.models.properties
910
import vscode_task_runner.utils.shell
1011
import vscode_task_runner.variables.resolve
1112
import vscode_task_runner.vscode.terminal_task_system
@@ -17,6 +18,7 @@
1718
vscode_task_runner.constants,
1819
vscode_task_runner.vscode.terminal_task_system,
1920
vscode_task_runner.utils.shell,
21+
vscode_task_runner.models.properties,
2022
)
2123
"""
2224
Sources where the platform key is used. Need to patch each one.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import pytest
2+
3+
from vscode_task_runner.models.enums import ShellQuotingEnum
4+
from vscode_task_runner.models.options import CommandOptions
5+
from vscode_task_runner.models.properties import (
6+
BaseCommandProperties,
7+
CommandProperties,
8+
)
9+
from vscode_task_runner.models.strings import QuotedStringConfig
10+
11+
12+
@pytest.fixture
13+
def command_properties() -> CommandProperties:
14+
return CommandProperties(
15+
windows=BaseCommandProperties(command="Windows"),
16+
linux=BaseCommandProperties(command="Linux"),
17+
osx=BaseCommandProperties(command="OSX"),
18+
)
19+
20+
21+
def test_command_properties_os_linux(
22+
linux: None, command_properties: CommandProperties
23+
) -> None:
24+
"""
25+
Ensure correct command properties are returned for Linux.
26+
"""
27+
assert command_properties.os is not None
28+
assert command_properties.os.command == "Linux"
29+
30+
31+
def test_command_properties_os_windows(
32+
windows: None, command_properties: CommandProperties
33+
) -> None:
34+
"""
35+
Ensure correct command properties are returned for Windows.
36+
"""
37+
assert command_properties.os is not None
38+
assert command_properties.os.command == "Windows"
39+
40+
41+
def test_command_properties_os_osx(
42+
osx: None, command_properties: CommandProperties
43+
) -> None:
44+
"""
45+
Ensure correct command properties are returned for OSX.
46+
"""
47+
assert command_properties.os is not None
48+
assert command_properties.os.command == "OSX"
49+
50+
51+
def test_command_properties_resolve_variables(
52+
linux: None, default_build_task_patch: None
53+
) -> None:
54+
"""
55+
Ensure resolve variables works for command properties.
56+
"""
57+
cp = CommandProperties(
58+
linux=BaseCommandProperties(command="${defaultBuildTask}"),
59+
)
60+
cp.resolve_variables()
61+
62+
assert cp.os is not None
63+
assert cp.os.command == "task1"
64+
65+
66+
def test_base_command_properties_resolve_variables(
67+
default_build_task_patch: None,
68+
) -> None:
69+
"""
70+
Ensure resolve variables works for base command properties.
71+
"""
72+
cp = BaseCommandProperties(
73+
command=QuotedStringConfig(
74+
value="${defaultBuildTask}", quoting=ShellQuotingEnum.escape
75+
),
76+
options=CommandOptions(),
77+
args=[
78+
QuotedStringConfig(
79+
value="${defaultBuildTask}", quoting=ShellQuotingEnum.escape
80+
),
81+
"${defaultBuildTask}",
82+
],
83+
)
84+
cp.resolve_variables()
85+
86+
assert cp.command == QuotedStringConfig(
87+
value="task1", quoting=ShellQuotingEnum.escape
88+
)
89+
assert cp.args == [
90+
QuotedStringConfig(value="task1", quoting=ShellQuotingEnum.escape),
91+
"task1",
92+
]

tests/test_models/test_model_shell.py

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
from vscode_task_runner.models.enums import ShellTypeEnum
44
from vscode_task_runner.models.shell import (
55
ShellConfiguration,
6-
ShellQuotingOptions,
7-
ShellQuotingOptionsEscape,
86
)
97

108

@@ -36,43 +34,11 @@ def test_type_detection(
3634
assert shell_config.type_ == expected_shell_type
3735

3836

39-
def test_shell_quoting_options_resolve_variables(
40-
default_build_task_patch: None,
41-
) -> None:
42-
"""
43-
Test resolving variables for ShellQuotingOptions
44-
"""
45-
# Test with substitution
46-
s = ShellQuotingOptions(
47-
strong="${defaultBuildTask}",
48-
weak="${defaultBuildTask}",
49-
escape="${defaultBuildTask}",
50-
)
51-
s.resolve_variables()
52-
assert s.strong == "task1"
53-
assert s.weak == "task1"
54-
assert s.escape == "task1"
55-
56-
5737
def test_shell_configuration_resolve_variables(default_build_task_patch: None) -> None:
5838
s = ShellConfiguration(
59-
executable="${defaultBuildTask}",
60-
args=["${defaultBuildTask}"],
61-
quoting=ShellQuotingOptions(
62-
strong="${defaultBuildTask}",
63-
weak="${defaultBuildTask}",
64-
escape=ShellQuotingOptionsEscape(
65-
escapeChar="${defaultBuildTask}", charsToEscape=["${defaultBuildTask}"]
66-
),
67-
),
39+
executable="${defaultBuildTask}", args=["${defaultBuildTask}"]
6840
)
6941

7042
s.resolve_variables()
7143
assert s.executable == "task1"
7244
assert s.args == ["task1"]
73-
assert isinstance(s.quoting, ShellQuotingOptions)
74-
assert s.quoting.strong == "task1"
75-
assert s.quoting.weak == "task1"
76-
assert isinstance(s.quoting.escape, ShellQuotingOptionsEscape)
77-
assert s.quoting.escape.escape_character == "task1"
78-
assert s.quoting.escape.characters_to_escape == ["task1"]

tests/test_vscode/test_terminal_task_system.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,6 @@
1515
@pytest.mark.parametrize(
1616
"shell_config, shell_quoting",
1717
[
18-
(
19-
ShellConfiguration(
20-
executable="/bin/bash",
21-
quoting=ShellQuotingOptions(strong="abc", weak="def"),
22-
),
23-
ShellQuotingOptions(strong="abc", weak="def"),
24-
),
2518
(
2619
ShellConfiguration(executable="/bin/bash"),
2720
DEFAULT_SHELL_QUOTING[ShellTypeEnum.SH],

vscode_task_runner/executor.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ def _new_task_env(task: Task) -> dict[str, str]:
2727
# from the global configuration to the task-specific configuration.
2828
# if the task defines options.
2929

30-
global_tasks_env = task._tasks.new_env_computed()
31-
task_env = task.new_env_computed()
30+
global_tasks_env = task._tasks.new_env_os()
31+
task_env = task.new_env_os()
3232

3333
# if the task defined any of its own options, discard global
3434
# otherwise, return the global task environment
@@ -58,11 +58,11 @@ def task_cwd(task: Task) -> Path:
5858
base = Path("/")
5959

6060
# task settings
61-
if task_cwd := task.cwd_computed():
61+
if task_cwd := task.cwd_os():
6262
cwd = base.joinpath(task_cwd)
6363

6464
# global settings
65-
elif global_cwd := task._tasks.cwd_computed():
65+
elif global_cwd := task._tasks.cwd_os():
6666
cwd = base.joinpath(global_cwd)
6767

6868
if not cwd.is_dir():
@@ -78,11 +78,11 @@ def task_command(task: Task) -> Optional[CommandStringConfig]:
7878
command = None
7979

8080
# task settings
81-
if task_command := task.command_computed():
81+
if task_command := task.command_os():
8282
command = task_command
8383

8484
# global settings
85-
elif global_command := task._tasks.command_computed():
85+
elif global_command := task._tasks.command_os():
8686
command = global_command
8787

8888
return command
@@ -92,12 +92,12 @@ def task_args(task: Task) -> list[CommandStringConfig]:
9292
"""
9393
Given a task, return the arguments to pass to the command.
9494
"""
95-
global_args = task._tasks.args_computed()
96-
task_args = task.args_computed()
95+
global_args = task._tasks.args_os()
96+
task_args = task.args_os()
9797

9898
# in the case that there is a global command defined, but not a task
9999
# command, tack on the task args to the global args
100-
task_command = task.command_computed()
100+
task_command = task.command_os()
101101
return global_args + task_args if task_command is None else task_args
102102

103103

@@ -108,11 +108,11 @@ def task_shell(task: Task) -> ShellConfiguration:
108108
shell = ShellConfiguration()
109109

110110
# task settings
111-
if task_shell := task.shell_computed():
111+
if task_shell := task.shell_os():
112112
shell = task_shell
113113

114114
# global settings
115-
elif global_shell := task._tasks.shell_computed():
115+
elif global_shell := task._tasks.shell_os():
116116
shell = global_shell
117117

118118
if not shell.executable:

vscode_task_runner/models/shell.py

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,6 @@ class ShellQuotingOptionsEscape(BaseModel):
1313
escape_character: str = Field(alias="escapeChar")
1414
characters_to_escape: list[str] = Field(alias="charsToEscape")
1515

16-
def resolve_variables(self) -> None:
17-
"""
18-
Resolve variables for this shell quoting options escape.
19-
"""
20-
21-
self.escape_character = resolve_variables_data(self.escape_character)
22-
self.characters_to_escape = resolve_variables_data(self.characters_to_escape)
23-
2416

2517
class ShellQuotingOptions(BaseModel):
2618
"""
@@ -42,20 +34,6 @@ class ShellQuotingOptions(BaseModel):
4234
The character used for weak quoting.
4335
"""
4436

45-
def resolve_variables(self) -> None:
46-
"""
47-
Resolve variables for this shell quoting options.
48-
"""
49-
50-
self.strong = resolve_variables_data(self.strong)
51-
52-
if isinstance(self.escape, ShellQuotingOptionsEscape):
53-
self.escape.resolve_variables()
54-
else:
55-
self.escape = resolve_variables_data(self.escape)
56-
57-
self.weak = resolve_variables_data(self.weak)
58-
5937

6038
class ShellConfiguration(BaseModel):
6139
"""
@@ -73,11 +51,12 @@ class ShellConfiguration(BaseModel):
7351
"""
7452
The args to be passed to the shell executable.
7553
"""
76-
quoting: Optional[ShellQuotingOptions] = None
54+
55+
# https://github.com/microsoft/vscode/blob/f4fb3e71208ebe861a00581c47d2a98bf23f68a2/src/vs/workbench/contrib/tasks/common/jsonSchemaCommon.ts#L58-L75
56+
_quoting: Optional[ShellQuotingOptions] = PrivateAttr(default=None)
7757
"""
78-
Which kind of quotes the shell supports.
58+
while appearing in the interface, not something the user can specify
7959
"""
80-
8160
_type: Optional[ShellTypeEnum] = PrivateAttr(default=None)
8261
"""
8362
Private attribute to cache the shell type.
@@ -120,12 +99,23 @@ def type_(self) -> ShellTypeEnum:
12099

121100
return self._type
122101

102+
@property
103+
def quoting(self) -> Optional[ShellQuotingOptions]:
104+
"""
105+
Which kind of quotes the shell supports.
106+
"""
107+
return self._quoting
108+
109+
@quoting.setter
110+
def quoting(self, value: Optional[ShellQuotingOptions]) -> None:
111+
"""
112+
Which kind of quotes the shell supports.
113+
"""
114+
self._quoting = value
115+
123116
def resolve_variables(self) -> None:
124117
"""
125118
Resolve variables for this shell config.
126119
"""
127120
self.executable = resolve_variables_data(self.executable)
128121
self.args = resolve_variables_data(self.args)
129-
130-
if self.quoting:
131-
self.quoting.resolve_variables()

vscode_task_runner/models/task.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def type_enum(self) -> TaskTypeEnum:
5757
except ValueError as e:
5858
raise UnsupportedTaskType(f"Unsupported task type {self.type_}") from e
5959

60-
def new_env_computed(self) -> dict[str, str]:
60+
def new_env_os(self) -> dict[str, str]:
6161
"""
6262
Computed explicitly defined environment variables for this task.
6363
Does not take into account the global environment variables.
@@ -71,7 +71,7 @@ def new_env_computed(self) -> dict[str, str]:
7171

7272
return task_env
7373

74-
def cwd_computed(self) -> Optional[str]:
74+
def cwd_os(self) -> Optional[str]:
7575
"""
7676
Computed working directoy for this task.
7777
Does not take into account the global working directory.
@@ -84,7 +84,7 @@ def cwd_computed(self) -> Optional[str]:
8484

8585
return cwd
8686

87-
def command_computed(self) -> Optional[CommandStringConfig]:
87+
def command_os(self) -> Optional[CommandStringConfig]:
8888
"""
8989
Computed command for this task.
9090
Does not take into account the global command.
@@ -97,7 +97,7 @@ def command_computed(self) -> Optional[CommandStringConfig]:
9797

9898
return command
9999

100-
def args_computed(self) -> list[CommandStringConfig]:
100+
def args_os(self) -> list[CommandStringConfig]:
101101
"""
102102
Computed args for this task.
103103
Does not take into account the global args.
@@ -110,7 +110,7 @@ def args_computed(self) -> list[CommandStringConfig]:
110110

111111
return args
112112

113-
def shell_computed(self) -> Optional[ShellConfiguration]:
113+
def shell_os(self) -> Optional[ShellConfiguration]:
114114
"""
115115
Computed shell for this task.
116116
Does not take into account the global shell.
@@ -190,6 +190,7 @@ def is_supported(self) -> bool:
190190
"""
191191
Check if the task is supported by VTR.
192192
"""
193+
# TODO!!!! not accurate, need to take into account global setting
193194
if self.is_background:
194195
# bakground tasks are not supported
195196
return False

vscode_task_runner/models/tasks.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def default_build_task(self) -> Union[Task, None]:
2525
"""
2626
Get the default build task.
2727
"""
28+
# TODO!!!, need to take into account the global group
2829
return next((task for task in self.tasks if task.is_default_build_task), None)
2930

3031
def model_post_init(self, context: Any) -> None:

0 commit comments

Comments
 (0)