-
-
Notifications
You must be signed in to change notification settings - Fork 7
Try a different module/package structure #151
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
Open
kinow
wants to merge
1
commit into
main
Choose a base branch
from
trying-different-struct
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,308 @@ | ||
"""WDL Expressions (literal values, arithmetic, comparison, conditional, string interpolation, array, map, and functions).""" | ||
|
||
import re | ||
from typing import Any, cast, Optional, Union, Tuple | ||
|
||
import WDL | ||
from wdl2cwl.errors import WDLSourceLine, ConversionException | ||
from wdl2cwl.util import get_input, nice_quote, ConversionContext | ||
|
||
|
||
def get_expr_ifthenelse( | ||
wdl_ifthenelse: WDL.Expr.IfThenElse, ctx: ConversionContext | ||
) -> str: | ||
"""Translate WDL IfThenElse Expressions.""" | ||
condition = get_expr(wdl_ifthenelse.condition, ctx) | ||
if_true = get_expr(wdl_ifthenelse.consequent, ctx) | ||
if_false = get_expr(wdl_ifthenelse.alternative, ctx) | ||
return f"{condition} ? {if_true} : {if_false}" | ||
|
||
|
||
def translate_wdl_placeholder( | ||
wdl_placeholder: WDL.Expr.Placeholder, ctx: ConversionContext | ||
) -> str: | ||
"""Translate WDL Expr Placeholder to a valid CWL expression.""" | ||
expr = wdl_placeholder.expr | ||
placeholder_expr = get_expr(expr, ctx) | ||
options = wdl_placeholder.options | ||
if options: | ||
if "true" in options: | ||
true_str = nice_quote(options["true"]) | ||
false_str = nice_quote(options["false"]) | ||
test_str = f"{placeholder_expr} ? {true_str} : {false_str}" | ||
is_optional = False | ||
if isinstance(expr, WDL.Expr.Get): | ||
is_optional = expr.type.optional | ||
elif isinstance(expr, WDL.Expr.Apply): | ||
is_optional = ( | ||
expr.arguments[0].type.optional | ||
and expr.function_name != "defined" # optimization | ||
) | ||
if not is_optional: | ||
return test_str | ||
else: | ||
if "default" in options: | ||
return ( | ||
f"{placeholder_expr} === null ? " | ||
f"{nice_quote(options['default'])} : {test_str}" | ||
) | ||
return f'{placeholder_expr} === null ? "" : {test_str}' | ||
elif "sep" in options: | ||
separator = options["sep"] | ||
assert isinstance(expr.type, WDL.Type.Array) | ||
item_type = expr.type.item_type | ||
if isinstance(item_type, WDL.Type.File): | ||
pl_holder_str = ( | ||
placeholder_expr + ".map(function(el) {return el.path})" | ||
f'.join("{separator}")' | ||
) | ||
else: | ||
pl_holder_str = f'{placeholder_expr}.join("{separator}")' | ||
if "default" in options and (expr.type.optional or item_type.optional): | ||
return ( | ||
f"{placeholder_expr} === null ? " | ||
f"{nice_quote(options['default'])} : {pl_holder_str}" | ||
) | ||
return pl_holder_str | ||
# options must contain only "default", no "sep" or "true"/"false" | ||
return ( | ||
f"{placeholder_expr} === null ? " | ||
f"{nice_quote(options['default'])} : {placeholder_expr}" | ||
) | ||
return placeholder_expr | ||
|
||
|
||
def get_expr_string(wdl_expr_string: WDL.Expr.String, ctx: ConversionContext) -> str: | ||
"""Translate WDL String Expressions.""" | ||
if wdl_expr_string.literal is not None: | ||
return str(wdl_expr_string.literal) | ||
parts = wdl_expr_string.parts | ||
q = cast(str, parts[0])[0] | ||
string = ( | ||
f"{q}{parts[1]}{q}" if isinstance(parts[1], str) else get_expr(parts[1], ctx) | ||
) | ||
if parts[2:-1]: | ||
string += " + " + " + ".join( | ||
f"{q}{part}{q}" if isinstance(part, str) else get_expr(part, ctx) | ||
for part in parts[2:-1] | ||
) | ||
return string | ||
|
||
|
||
def get_expr_name(wdl_expr: WDL.Expr.Ident) -> str: | ||
"""Extract name from WDL expr.""" | ||
return get_input(wdl_expr.name) | ||
|
||
|
||
def get_expr(wdl_expr: WDL.Expr.Base, ctx: ConversionContext) -> str: | ||
"""Translate WDL Expressions.""" | ||
if isinstance(wdl_expr, WDL.Expr.Apply): | ||
return get_expr_apply(wdl_expr, ctx) | ||
elif isinstance(wdl_expr, WDL.Expr.Get): | ||
return get_expr_get(wdl_expr, ctx) | ||
elif isinstance(wdl_expr, WDL.Expr.IfThenElse): | ||
return get_expr_ifthenelse(wdl_expr, ctx) | ||
elif isinstance(wdl_expr, WDL.Expr.Placeholder): | ||
return translate_wdl_placeholder(wdl_expr, ctx) | ||
elif isinstance(wdl_expr, WDL.Expr.String): | ||
return get_expr_string(wdl_expr, ctx) | ||
elif isinstance(wdl_expr, WDL.Expr.Boolean) and wdl_expr.literal: | ||
return str(wdl_expr.literal) # "true" not "True" | ||
elif ( | ||
isinstance( | ||
wdl_expr, | ||
( | ||
WDL.Expr.Boolean, | ||
WDL.Expr.Int, | ||
WDL.Expr.Float, | ||
), | ||
) | ||
and wdl_expr.literal | ||
): | ||
return str(wdl_expr.literal.value) | ||
elif isinstance(wdl_expr, WDL.Expr.Array): | ||
return "[ " + ", ".join(get_expr(item, ctx) for item in wdl_expr.items) + " ]" | ||
elif isinstance(wdl_expr, WDL.Expr.Map): | ||
return ( | ||
"{ " | ||
+ ", ".join( | ||
f"{get_expr(key, ctx)}: {get_expr(value, ctx)}" | ||
for key, value in wdl_expr.items | ||
) | ||
+ " }" | ||
) | ||
else: # pragma: no cover | ||
raise WDLSourceLine(wdl_expr, ConversionException).makeError( | ||
f"The expression '{wdl_expr}' is not handled yet." | ||
) | ||
|
||
|
||
_BINARY_OPS = { | ||
"_gt": ">", | ||
"_lor": "||", | ||
"_neq": "!==", | ||
"_lt": "<", | ||
"_mul": "*", | ||
"_eqeq": "===", | ||
"_div": "/", | ||
"_sub": "-", | ||
} | ||
|
||
_SINGLE_ARG_FN = { # implemented elsewhere, just return the argument | ||
"read_string", | ||
"read_float", | ||
"glob", | ||
"read_int", | ||
"read_boolean", | ||
"read_tsv", | ||
"read_lines", | ||
} | ||
|
||
|
||
def get_expr_apply(wdl_apply_expr: WDL.Expr.Apply, ctx: ConversionContext) -> str: | ||
"""Translate WDL Apply Expressions.""" | ||
# N.B: This import here avoids circular dependency error when loading the modules. | ||
from wdl2cwl import functions | ||
|
||
function_name = wdl_apply_expr.function_name | ||
arguments = wdl_apply_expr.arguments | ||
if not arguments: | ||
raise WDLSourceLine(wdl_apply_expr, ConversionException).makeError( | ||
f"The '{wdl_apply_expr}' expression has no arguments." | ||
) | ||
treat_as_optional = wdl_apply_expr.type.optional | ||
|
||
if function_name in _BINARY_OPS: | ||
left_operand, right_operand = arguments | ||
left_operand_expr = get_expr(left_operand, ctx) | ||
right_operand_expr = get_expr(right_operand, ctx) | ||
return f"{left_operand_expr} {_BINARY_OPS[function_name]} {right_operand_expr}" | ||
elif function_name in _SINGLE_ARG_FN: | ||
only_arg = arguments[0] | ||
return get_expr(only_arg, ctx) | ||
elif hasattr(functions, function_name): | ||
# Call the function if we have it in our wdl2cwl.functions module | ||
kwargs = { | ||
"treat_as_optional": treat_as_optional, | ||
"wdl_apply_expr": wdl_apply_expr, | ||
} | ||
return cast( | ||
str, | ||
getattr(functions, function_name)(arguments, ctx, **kwargs), | ||
) | ||
raise WDLSourceLine(wdl_apply_expr, ConversionException).makeError( | ||
f"Function name '{function_name}' not yet handled." | ||
) | ||
|
||
|
||
def get_expr_get(wdl_get_expr: WDL.Expr.Get, ctx: ConversionContext) -> str: | ||
"""Translate WDL Get Expressions.""" | ||
member = wdl_get_expr.member | ||
|
||
if not member: | ||
return get_expr_ident(wdl_get_expr.expr, ctx) # type: ignore[arg-type] | ||
struct_name = get_expr(wdl_get_expr.expr, ctx) | ||
member_str = f"{struct_name}.{member}" | ||
return ( | ||
member_str | ||
if not isinstance(wdl_get_expr.type, WDL.Type.File) | ||
else f"{member_str}.path" | ||
) | ||
|
||
|
||
def get_expr_ident(wdl_ident_expr: WDL.Expr.Ident, ctx: ConversionContext) -> str: | ||
"""Translate WDL Ident Expressions.""" | ||
id_name = wdl_ident_expr.name | ||
referee = wdl_ident_expr.referee | ||
optional = wdl_ident_expr.type.optional | ||
if referee: | ||
with WDLSourceLine(referee, ConversionException): | ||
if isinstance(referee, WDL.Tree.Call): | ||
return id_name | ||
if referee.expr and ( | ||
wdl_ident_expr.name in ctx.optional_cwl_null | ||
or wdl_ident_expr.name not in ctx.non_static_values | ||
): | ||
return get_expr(referee.expr, ctx) | ||
ident_name = get_input(id_name) | ||
if optional and isinstance(wdl_ident_expr.type, WDL.Type.File): | ||
# To prevent null showing on the terminal for inputs of type File | ||
name_with_file_check = get_expr_name_with_is_file_check(wdl_ident_expr) | ||
return f'{ident_name} === null ? "" : {name_with_file_check}' | ||
return ( | ||
ident_name | ||
if not isinstance(wdl_ident_expr.type, WDL.Type.File) | ||
else f"{ident_name}.path" | ||
) | ||
|
||
|
||
def get_expr_name_with_is_file_check(wdl_expr: WDL.Expr.Ident) -> str: | ||
"""Extract name from WDL expr and check if it's a file path.""" | ||
expr_name = get_input(wdl_expr.name) | ||
is_file = isinstance(wdl_expr.type, WDL.Type.File) | ||
return expr_name if not is_file else f"{expr_name}.path" | ||
|
||
|
||
def get_literal_value(expr: WDL.Expr.Base) -> Optional[Any]: | ||
"""Recursively get a literal value.""" | ||
literal = expr.literal | ||
if literal: | ||
if hasattr(expr.parent, "type") and isinstance(expr.parent.type, WDL.Type.File): # type: ignore[attr-defined] | ||
return {"class": "File", "path": literal.value} | ||
value = literal.value | ||
if isinstance(expr.type, WDL.Type.Map): | ||
return {key.value: val.value for key, val in value} | ||
if isinstance(value, list): | ||
result = [] | ||
for item in value: | ||
if hasattr(expr.parent, "type") and isinstance(expr.parent.type.item_type, WDL.Type.File): # type: ignore[attr-defined] | ||
result.append({"class": "File", "path": item.value}) | ||
else: | ||
result.append(item.value) | ||
return result | ||
return value | ||
return None | ||
|
||
|
||
def get_step_input_expr( | ||
wf_expr: Union[WDL.Expr.Get, WDL.Expr.String], ctx: ConversionContext | ||
) -> Tuple[str, Optional[str]]: | ||
""" | ||
Get name of expression referenced in workflow call inputs. | ||
|
||
Returns a tuple of the source plus any needed "valueFrom" expression. | ||
""" | ||
with WDLSourceLine(wf_expr, ConversionException): | ||
if isinstance(wf_expr, WDL.Expr.String): | ||
return get_expr_string(wf_expr, ctx)[1:-1], None | ||
elif isinstance(wf_expr, WDL.Expr.Get): | ||
if isinstance(wf_expr.expr, WDL.Expr.Ident): | ||
member = None | ||
id_name = wf_expr.expr.name | ||
referee = wf_expr.expr.referee | ||
if referee and isinstance(referee, WDL.Tree.Scatter): | ||
scatter_name, value_from = get_step_input_expr(referee.expr, ctx) # type: ignore[arg-type] | ||
ctx.scatter_names.append(scatter_name) | ||
return scatter_name, value_from | ||
return id_name, None | ||
elif isinstance(wf_expr.expr, WDL.Expr.Get): | ||
member = str(wf_expr.member) | ||
ident = cast(WDL.Expr.Ident, wf_expr.expr.expr) | ||
id_name = ident.name | ||
elif isinstance(wf_expr, WDL.Expr.Apply): | ||
expr_str = get_expr(wf_expr, ctx) | ||
if expr_str.count("inputs") == 1: | ||
id_name = re.match(r"inputs\.*?[ \.](.*?)[. ]", expr_str).groups()[0] | ||
value_from = "self" + expr_str.partition(f"inputs.{id_name}")[2] | ||
return id_name, value_from | ||
else: | ||
return get_literal_value(wf_expr), None | ||
return id_name, f"self.{member}" if member else None | ||
|
||
|
||
__all__ = [ | ||
"get_expr", | ||
"get_expr_string", | ||
"get_step_input_expr", | ||
"translate_wdl_placeholder", | ||
] |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.