Skip to content

Commit 68c5b9e

Browse files
committed
Trying to move things to modules
1 parent 9781f7b commit 68c5b9e

File tree

8 files changed

+628
-458
lines changed

8 files changed

+628
-458
lines changed

wdl2cwl/errors.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,7 @@ def makeError(self, msg: str) -> Any:
6969
else:
7070
errs.append(f"{lead} {m}")
7171
return self.raise_type("\n".join(errs))
72+
73+
74+
class ConversionException(Exception):
75+
"""Error during conversion."""

wdl2cwl/expr.py

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
"""WDL Expressions (literal values, arithmetic, comparison, conditional, string interpolation, array, map, and functions)."""
2+
3+
from typing import Union, Any, cast
4+
5+
import WDL
6+
from wdl2cwl.errors import WDLSourceLine, ConversionException
7+
from wdl2cwl.util import get_input, ConversionContext
8+
9+
10+
def get_literal_name(
11+
expr: Union[WDL.Expr.Boolean, WDL.Expr.Int, WDL.Expr.Float, WDL.Expr.Array]
12+
) -> str:
13+
"""Translate WDL Boolean, Int, Float, or Array Expression."""
14+
# if the literal expr is used inside WDL.Expr.Apply
15+
# the literal value is what's needed
16+
parent = expr.parent # type: ignore[union-attr]
17+
if isinstance(parent, (WDL.Expr.Apply, WDL.Expr.IfThenElse)):
18+
return expr.literal.value # type: ignore
19+
raise WDLSourceLine(expr, ConversionException).makeError(
20+
f"The parent expression for {expr} is not WDL.Expr.Apply, but {parent}."
21+
)
22+
23+
24+
def get_expr_ifthenelse(
25+
wdl_ifthenelse: WDL.Expr.IfThenElse, ctx: ConversionContext
26+
) -> str:
27+
"""Translate WDL IfThenElse Expressions."""
28+
condition = get_expr(wdl_ifthenelse.condition, ctx)
29+
if_true = get_expr(wdl_ifthenelse.consequent, ctx)
30+
if_false = get_expr(wdl_ifthenelse.alternative, ctx)
31+
return f"{condition} ? {if_true} : {if_false}"
32+
33+
34+
def translate_wdl_placeholder(
35+
wdl_placeholder: WDL.Expr.Placeholder, ctx: ConversionContext
36+
) -> str:
37+
"""Translate WDL Expr Placeholder to a valid CWL command string."""
38+
cwl_command_str = ""
39+
expr = wdl_placeholder.expr
40+
if expr is None:
41+
raise WDLSourceLine(wdl_placeholder, ConversionException).makeError(
42+
f"Placeholder '{wdl_placeholder}' has no expr."
43+
)
44+
placeholder_expr = get_expr(expr, ctx)
45+
options = wdl_placeholder.options
46+
if options:
47+
if "true" in options:
48+
true_value = options["true"]
49+
false_value = options["false"]
50+
true_str = f'"{true_value}"' if '"' not in true_value else f"'{true_value}'"
51+
false_str = (
52+
f'"{false_value}"' if '"' not in false_value else f"'{false_value}'"
53+
)
54+
is_optional = False
55+
if isinstance(expr, WDL.Expr.Get):
56+
is_optional = expr.type.optional
57+
elif isinstance(expr, WDL.Expr.Apply):
58+
is_optional = expr.arguments[0].type.optional
59+
if not is_optional:
60+
cwl_command_str = f"$({placeholder_expr} ? {true_str} : {false_str})"
61+
else:
62+
cwl_command_str = (
63+
f"$({placeholder_expr} === null ? {false_str} : {true_str})"
64+
)
65+
elif "sep" in options:
66+
seperator = options["sep"]
67+
if isinstance(expr.type, WDL.Type.Array):
68+
item_type: WDL.Expr.Base = expr.type.item_type # type: ignore
69+
if isinstance(item_type, WDL.Type.String):
70+
cwl_command_str = f'$({placeholder_expr}.join("{seperator}"))'
71+
elif isinstance(item_type, WDL.Type.File):
72+
cwl_command_str = (
73+
f"$({placeholder_expr}.map("
74+
+ 'function(el) {return el.path}).join("'
75+
+ seperator
76+
+ '"))'
77+
)
78+
else:
79+
raise WDLSourceLine(wdl_placeholder, ConversionException).makeError(
80+
f"{wdl_placeholder} with separator and item type {item_type} is not yet handled"
81+
)
82+
else:
83+
raise WDLSourceLine(wdl_placeholder, ConversionException).makeError(
84+
f"{wdl_placeholder} with expr of type {expr.type} is not yet handled"
85+
)
86+
else:
87+
raise WDLSourceLine(wdl_placeholder, ConversionException).makeError(
88+
f"Placeholders with options {options} are not yet handled."
89+
)
90+
else:
91+
# for the one case where the $(input.some_input_name) is used within the placeholder_expr
92+
# we return the placeholder_expr without enclosing in another $()
93+
cwl_command_str = (
94+
f"$({placeholder_expr})"
95+
if placeholder_expr[-1] != ")"
96+
else placeholder_expr
97+
)
98+
# sometimes placeholders are used inside WDL.Expr.String.
99+
# with the parent and grand_parent we can confirm that we are in
100+
# the command string (WDL.Expr.String) and task (WDL.Tree.Task) respectively
101+
parent = wdl_placeholder.parent # type: ignore
102+
grand_parent = parent.parent
103+
return (
104+
cwl_command_str
105+
if isinstance(parent, WDL.Expr.String)
106+
and isinstance(grand_parent, WDL.Tree.Task)
107+
else cwl_command_str[2:-1]
108+
)
109+
110+
111+
def get_expr_string(wdl_expr_string: WDL.Expr.String, ctx: ConversionContext) -> str:
112+
"""Translate WDL String Expressions."""
113+
if wdl_expr_string.literal is not None:
114+
return f'"{wdl_expr_string.literal.value}"'
115+
string = ""
116+
parts = wdl_expr_string.parts
117+
for index, part in enumerate(parts[1:-1], start=1):
118+
if isinstance(
119+
part,
120+
(WDL.Expr.Placeholder, WDL.Expr.Apply, WDL.Expr.Get, WDL.Expr.Ident),
121+
):
122+
placeholder = get_expr(part, ctx)
123+
part = (
124+
"" if parts[index - 1] == '"' or parts[index - 1] == "'" else "' + " # type: ignore
125+
)
126+
part += placeholder
127+
part += (
128+
"" if parts[index + 1] == '"' or parts[index + 1] == "'" else " + '" # type: ignore
129+
)
130+
string += part
131+
# condition to determine if the opening and closing quotes should be added to string
132+
# for cases where a placeholder begins or ends a WDL.Expr.String
133+
if type(parts[1]) == str:
134+
string = "'" + string
135+
if type(parts[-2]) == str:
136+
string = string + "'"
137+
return string
138+
139+
140+
def get_expr_name(wdl_expr: WDL.Expr.Ident) -> str:
141+
"""Extract name from WDL expr."""
142+
if not hasattr(wdl_expr, "name"):
143+
raise WDLSourceLine(wdl_expr, ConversionException).makeError(
144+
f"{type(wdl_expr)} has not attribute 'name'"
145+
)
146+
return get_input(wdl_expr.name)
147+
148+
149+
def get_expr(wdl_expr: Any, ctx: ConversionContext) -> str:
150+
"""Translate WDL Expressions."""
151+
if isinstance(wdl_expr, WDL.Expr.Apply):
152+
return get_expr_apply(wdl_expr, ctx)
153+
elif isinstance(wdl_expr, WDL.Expr.Get):
154+
return get_expr_get(wdl_expr, ctx)
155+
elif isinstance(wdl_expr, WDL.Expr.IfThenElse):
156+
return get_expr_ifthenelse(wdl_expr, ctx)
157+
elif isinstance(wdl_expr, WDL.Expr.Placeholder):
158+
return translate_wdl_placeholder(wdl_expr, ctx)
159+
elif isinstance(wdl_expr, WDL.Expr.String):
160+
return get_expr_string(wdl_expr, ctx)
161+
elif isinstance(wdl_expr, WDL.Tree.Decl):
162+
return get_expr(wdl_expr.expr, ctx)
163+
elif isinstance(
164+
wdl_expr,
165+
(
166+
WDL.Expr.Boolean,
167+
WDL.Expr.Int,
168+
WDL.Expr.Float,
169+
WDL.Expr.Array,
170+
),
171+
):
172+
return get_literal_name(wdl_expr)
173+
else:
174+
raise WDLSourceLine(wdl_expr, ConversionException).makeError(
175+
f"The expression '{wdl_expr}' is not handled yet."
176+
)
177+
178+
179+
def get_expr_apply(wdl_apply_expr: WDL.Expr.Apply, ctx: ConversionContext) -> str:
180+
"""Translate WDL Apply Expressions."""
181+
# N.B: This import here avoids circular dependency error when loading the modules.
182+
from wdl2cwl import functions
183+
184+
function_name = wdl_apply_expr.function_name
185+
arguments = wdl_apply_expr.arguments
186+
if not arguments:
187+
raise WDLSourceLine(wdl_apply_expr, ConversionException).makeError(
188+
f"The '{wdl_apply_expr}' expression has no arguments."
189+
)
190+
treat_as_optional = wdl_apply_expr.type.optional
191+
192+
# Call the function if we have it in our wdl2cwl.functions module
193+
if hasattr(functions, function_name):
194+
return cast(
195+
str,
196+
getattr(functions, function_name)(
197+
arguments,
198+
ctx,
199+
{
200+
"treat_as_optional": treat_as_optional,
201+
"wdl_apply_expr": wdl_apply_expr,
202+
},
203+
),
204+
)
205+
206+
raise WDLSourceLine(wdl_apply_expr, ConversionException).makeError(
207+
f"Function name '{function_name}' not yet handled."
208+
)
209+
210+
211+
def get_expr_get(wdl_get_expr: WDL.Expr.Get, ctx: ConversionContext) -> str:
212+
"""Translate WDL Get Expressions."""
213+
member = wdl_get_expr.member
214+
if not member:
215+
return get_expr_ident(wdl_get_expr.expr, ctx) # type: ignore
216+
struct_name = get_expr(wdl_get_expr.expr, ctx)
217+
member_str = f"{struct_name}.{member}"
218+
return (
219+
member_str
220+
if not isinstance(wdl_get_expr.type, WDL.Type.File)
221+
else f"{member_str}.path"
222+
)
223+
224+
225+
def get_expr_ident(wdl_ident_expr: WDL.Expr.Ident, ctx: ConversionContext) -> str:
226+
"""Translate WDL Ident Expressions."""
227+
id_name = wdl_ident_expr.name
228+
ident_name = get_input(id_name)
229+
referee: Any = wdl_ident_expr.referee
230+
optional = wdl_ident_expr.type.optional
231+
if referee:
232+
with WDLSourceLine(referee, ConversionException):
233+
if isinstance(referee, WDL.Tree.Call):
234+
return id_name
235+
if referee.expr and (
236+
wdl_ident_expr.name in ctx.optional_cwl_null
237+
or wdl_ident_expr.name not in ctx.non_static_values
238+
):
239+
return get_expr(referee.expr, ctx)
240+
if optional and isinstance(wdl_ident_expr.type, WDL.Type.File):
241+
# To prevent null showing on the terminal for inputs of type File
242+
name_with_file_check = get_expr_name_with_is_file_check(wdl_ident_expr)
243+
return f'{ident_name} === null ? "" : {name_with_file_check}'
244+
return (
245+
ident_name
246+
if not isinstance(wdl_ident_expr.type, WDL.Type.File)
247+
else f"{ident_name}.path"
248+
)
249+
250+
251+
def get_expr_name_with_is_file_check(wdl_expr: WDL.Expr.Ident) -> str:
252+
"""Extract name from WDL expr and check if it's a file path."""
253+
if wdl_expr is None or not hasattr(wdl_expr, "name"):
254+
raise WDLSourceLine(wdl_expr, ConversionException).makeError(
255+
f"{type(wdl_expr)} has not attribute 'name'"
256+
)
257+
expr_name = get_input(wdl_expr.name)
258+
is_file = isinstance(wdl_expr.type, WDL.Type.File)
259+
return expr_name if not is_file else f"{expr_name}.path"
260+
261+
262+
__all__ = ["get_expr", "get_expr_string", "translate_wdl_placeholder"]

0 commit comments

Comments
 (0)