mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-07 23:09:21 +01:00
parsing: Escape str interpolation in dict unpacking.
Use subprocess.list2cmdline (windows) / shlex.quote Closes #8203 Closes #8370
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import os
|
||||
import re
|
||||
import typing
|
||||
from collections.abc import Iterable, Mapping
|
||||
@@ -70,6 +71,16 @@ def embrace(s: str):
|
||||
return BRACE_OPEN + s + BRACE_CLOSE
|
||||
|
||||
|
||||
def escape_str(value):
|
||||
if os.name == "nt":
|
||||
from subprocess import list2cmdline
|
||||
|
||||
return list2cmdline([value])
|
||||
from shlex import quote
|
||||
|
||||
return quote(value)
|
||||
|
||||
|
||||
@singledispatch
|
||||
def to_str(obj) -> str:
|
||||
return str(obj)
|
||||
@@ -97,12 +108,12 @@ def _(obj: dict):
|
||||
result += f"--no-{k} "
|
||||
|
||||
elif isinstance(v, str):
|
||||
result += f"--{k} '{v}' "
|
||||
result += f"--{k} {escape_str(v)} "
|
||||
|
||||
elif isinstance(v, Iterable):
|
||||
for n, i in enumerate(v):
|
||||
if isinstance(i, str):
|
||||
i = f"'{i}'"
|
||||
i = escape_str(i)
|
||||
elif isinstance(i, Iterable):
|
||||
raise ParseError(
|
||||
f"Cannot interpolate nested iterable in '{k}'"
|
||||
|
||||
@@ -6,6 +6,7 @@ import pytest
|
||||
from dvc.dependency import _merge_params
|
||||
from dvc.parsing import DEFAULT_PARAMS_FILE, DataResolver
|
||||
from dvc.parsing.context import recurse_not_a_node
|
||||
from dvc.parsing.interpolate import escape_str
|
||||
|
||||
from . import (
|
||||
CONTEXT_DATA,
|
||||
@@ -272,14 +273,17 @@ def test_cmd_dict(tmp_dir, dvc, bool_config, list_config):
|
||||
if list_config:
|
||||
conf["parsing"]["list"] = list_config
|
||||
|
||||
string = "spaced string"
|
||||
mixed_quote_string = "quote\"'d"
|
||||
data = {
|
||||
"dict": {
|
||||
"foo": "foo",
|
||||
"bar": 2,
|
||||
"string": "spaced string",
|
||||
"string": string,
|
||||
"mixed_quote_string": mixed_quote_string,
|
||||
"bool": True,
|
||||
"bool-false": False,
|
||||
"list": [1, 2, "foo"],
|
||||
"list": [1, 2, "foo", mixed_quote_string],
|
||||
"nested": {"foo": "foo"},
|
||||
}
|
||||
}
|
||||
@@ -296,9 +300,10 @@ def test_cmd_dict(tmp_dir, dvc, bool_config, list_config):
|
||||
bool_resolved = " --bool --no-bool-false"
|
||||
|
||||
if list_config is None or list_config == "nargs":
|
||||
list_resolved = " --list 1 2 'foo'"
|
||||
list_resolved = f" --list 1 2 foo {escape_str(mixed_quote_string)}"
|
||||
else:
|
||||
list_resolved = " --list 1 --list 2 --list 'foo'"
|
||||
list_resolved = " --list 1 --list 2 --list foo"
|
||||
list_resolved += f" --list {escape_str(mixed_quote_string)}"
|
||||
|
||||
assert_stage_equal(
|
||||
resolver.resolve(),
|
||||
@@ -306,11 +311,12 @@ def test_cmd_dict(tmp_dir, dvc, bool_config, list_config):
|
||||
"stages": {
|
||||
"stage1": {
|
||||
"cmd": "python script.py"
|
||||
" --foo 'foo' --bar 2"
|
||||
" --string 'spaced string'"
|
||||
" --foo foo --bar 2"
|
||||
f" --string {escape_str(string)}"
|
||||
f" --mixed_quote_string {escape_str(mixed_quote_string)}"
|
||||
f"{bool_resolved}"
|
||||
f"{list_resolved}"
|
||||
" --nested.foo 'foo'"
|
||||
" --nested.foo foo"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user