Skip to content

Commit 98c3e76

Browse files
authored
convert param refs to exprs (#1371)
1 parent aa4bb4a commit 98c3e76

File tree

2 files changed

+141
-23
lines changed

2 files changed

+141
-23
lines changed

cwltool/expression.py

+53-23
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,10 @@ def evaluator(
270270
)
271271

272272

273+
def _convert_dumper(string: str) -> str:
274+
return "{} + ".format(json.dumps(string))
275+
276+
273277
def interpolate(
274278
scan: str,
275279
rootvars: CWLObjectType,
@@ -281,57 +285,83 @@ def interpolate(
281285
js_console: bool = False,
282286
strip_whitespace: bool = True,
283287
escaping_behavior: int = 2,
288+
convert_to_expression: bool = False,
284289
) -> Optional[CWLOutputType]:
290+
"""
291+
Interpolate and evaluate.
292+
293+
Note: only call with convert_to_expression=True on CWL Expressions in $()
294+
form that need interpolation.
295+
"""
285296
if strip_whitespace:
286297
scan = scan.strip()
287298
parts = []
299+
if convert_to_expression:
300+
dump = _convert_dumper
301+
parts.append("${return ")
302+
else:
303+
dump = lambda x: x
288304
w = scanner(scan)
289305
while w:
290-
parts.append(scan[0 : w[0]])
306+
if convert_to_expression:
307+
parts.append('"{}" + '.format(scan[0 : w[0]]))
308+
else:
309+
parts.append(scan[0 : w[0]])
291310

292311
if scan[w[0]] == "$":
293-
e = evaluator(
294-
scan[w[0] + 1 : w[1]],
295-
jslib,
296-
rootvars,
297-
timeout,
298-
fullJS=fullJS,
299-
force_docker_pull=force_docker_pull,
300-
debug=debug,
301-
js_console=js_console,
302-
)
303-
if w[0] == 0 and w[1] == len(scan) and len(parts) <= 1:
304-
return e
305-
leaf = json_dumps(e, sort_keys=True)
306-
if leaf[0] == '"':
307-
leaf = json.loads(leaf)
308-
parts.append(leaf)
312+
if not convert_to_expression:
313+
e = evaluator(
314+
scan[w[0] + 1 : w[1]],
315+
jslib,
316+
rootvars,
317+
timeout,
318+
fullJS=fullJS,
319+
force_docker_pull=force_docker_pull,
320+
debug=debug,
321+
js_console=js_console,
322+
)
323+
if w[0] == 0 and w[1] == len(scan) and len(parts) <= 1:
324+
return e
325+
326+
leaf = json_dumps(e, sort_keys=True)
327+
if leaf[0] == '"':
328+
leaf = json.loads(leaf)
329+
parts.append(leaf)
330+
else:
331+
parts.append(
332+
"function(){var item ="
333+
+ scan[w[0] : w[1]][2:-1]
334+
+ '; if (typeof(item) === "string"){ return item; } else { return JSON.stringify(item); }}() + '
335+
)
309336
elif scan[w[0]] == "\\":
310337
if escaping_behavior == 1:
311338
# Old behavior. Just skip the next character.
312339
e = scan[w[1] - 1]
313-
parts.append(e)
340+
parts.append(dump(e))
314341
elif escaping_behavior == 2:
315342
# Backslash quoting requires a three character lookahead.
316343
e = scan[w[0] : w[1] + 1]
317344
if e in ("\\$(", "\\${"):
318345
# Suppress start of a parameter reference, drop the
319346
# backslash.
320-
parts.append(e[1:])
347+
parts.append(dump(e[1:]))
321348
w = (w[0], w[1] + 1)
322349
elif e[1] == "\\":
323350
# Double backslash, becomes a single backslash
324-
parts.append("\\")
351+
parts.append(dump("\\"))
325352
else:
326353
# Some other text, add it as-is (including the
327354
# backslash) and resume scanning.
328-
parts.append(e[:2])
355+
parts.append(dump(e[:2]))
329356
else:
330357
raise Exception("Unknown escaping behavior %s" % escaping_behavior)
331-
332358
scan = scan[w[1] :]
333359
w = scanner(scan)
334-
parts.append(scan)
360+
if convert_to_expression:
361+
parts.append('"{}"'.format(scan))
362+
parts.append(";}")
363+
else:
364+
parts.append(scan)
335365
return "".join(parts)
336366

337367

tests/test_examples.py

+88
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,94 @@ def test_expression_interpolate(pattern: str, expected: Any) -> None:
132132
assert expr.interpolate(pattern, interpolate_input) == expected
133133

134134

135+
parameter_to_expressions = [
136+
(
137+
"-$(foo)",
138+
r"""-{"bar":{"baz":"zab1"},"b ar":{"baz":2},"b'ar":{"baz":true},"b\"ar":{"baz":null}}""",
139+
),
140+
("-$(foo.bar)", """-{"baz":"zab1"}"""),
141+
("-$(foo['bar'])", """-{"baz":"zab1"}"""),
142+
('-$(foo["bar"])', """-{"baz":"zab1"}"""),
143+
("-$(foo.bar.baz)", "-zab1"),
144+
("-$(foo['bar'].baz)", "-zab1"),
145+
("-$(foo['bar'][\"baz\"])", "-zab1"),
146+
("-$(foo.bar['baz'])", "-zab1"),
147+
("-$(foo['b ar'].baz)", "-2"),
148+
("-$(foo['b\\'ar'].baz)", "-true"),
149+
('-$(foo["b\\\'ar"].baz)', "-true"),
150+
("-$(foo['b\\\"ar'].baz)", "-null"),
151+
("$(foo.bar) $(foo.bar)", """{"baz":"zab1"} {"baz":"zab1"}"""),
152+
("$(foo['bar']) $(foo['bar'])", """{"baz":"zab1"} {"baz":"zab1"}"""),
153+
('$(foo["bar"]) $(foo["bar"])', """{"baz":"zab1"} {"baz":"zab1"}"""),
154+
("$(foo.bar.baz) $(foo.bar.baz)", "zab1 zab1"),
155+
("$(foo['bar'].baz) $(foo['bar'].baz)", "zab1 zab1"),
156+
("$(foo['bar'][\"baz\"]) $(foo['bar'][\"baz\"])", "zab1 zab1"),
157+
("$(foo.bar['baz']) $(foo.bar['baz'])", "zab1 zab1"),
158+
("$(foo['b ar'].baz) $(foo['b ar'].baz)", "2 2"),
159+
("$(foo['b\\'ar'].baz) $(foo['b\\'ar'].baz)", "true true"),
160+
('$(foo["b\\\'ar"].baz) $(foo["b\\\'ar"].baz)', "true true"),
161+
("$(foo['b\\\"ar'].baz) $(foo['b\\\"ar'].baz)", "null null"),
162+
]
163+
164+
165+
@pytest.mark.parametrize("pattern,expected", parameter_to_expressions) # type: ignore
166+
def test_parameter_to_expression(pattern: str, expected: Any) -> None:
167+
"""Test the interpolate convert_to_expression feature."""
168+
assert (
169+
expr.interpolate(
170+
expr.interpolate(pattern, {}, convert_to_expression=True),
171+
None,
172+
jslib=expr.jshead([], interpolate_input),
173+
fullJS=True,
174+
debug=True,
175+
)
176+
== expected
177+
)
178+
179+
180+
param_to_expr_interpolate_escapebehavior = (
181+
("\\$(foo.bar.baz)", "$(foo.bar.baz)", 1),
182+
("\\\\$(foo.bar.baz)", "\\zab1", 1),
183+
("\\\\\\$(foo.bar.baz)", "\\$(foo.bar.baz)", 1),
184+
("\\\\\\\\$(foo.bar.baz)", "\\\\zab1", 1),
185+
("\\$foo", "$foo", 1),
186+
("\\foo", "foo", 1),
187+
("\\x", "x", 1),
188+
("\\\\x", "\\x", 1),
189+
("\\\\\\x", "\\x", 1),
190+
("\\\\\\\\x", "\\\\x", 1),
191+
("\\$(foo.bar.baz)", "$(foo.bar.baz)", 2),
192+
("\\\\$(foo.bar.baz)", "\\zab1", 2),
193+
("\\\\\\$(foo.bar.baz)", "\\$(foo.bar.baz)", 2),
194+
("\\\\\\\\$(foo.bar.baz)", "\\\\zab1", 2),
195+
("\\$foo", "\\$foo", 2),
196+
("\\foo", "\\foo", 2),
197+
("\\x", "\\x", 2),
198+
("\\\\x", "\\x", 2),
199+
("\\\\\\x", "\\\\x", 2),
200+
("\\\\\\\\x", "\\\\x", 2),
201+
)
202+
203+
204+
@pytest.mark.parametrize("pattern,expected,behavior", param_to_expr_interpolate_escapebehavior) # type: ignore
205+
def test_parameter_to_expression_interpolate_escapebehavior(
206+
pattern: str, expected: str, behavior: int
207+
) -> None:
208+
"""Test escaping behavior in an convert_to_expression context."""
209+
assert (
210+
expr.interpolate(
211+
expr.interpolate(
212+
pattern, {}, escaping_behavior=behavior, convert_to_expression=True
213+
),
214+
None,
215+
jslib=expr.jshead([], interpolate_input),
216+
fullJS=True,
217+
debug=True,
218+
)
219+
== expected
220+
)
221+
222+
135223
interpolate_bad_parameters = [
136224
("$(fooz)"),
137225
("$(foo.barz)"),

0 commit comments

Comments
 (0)