Skip to content

Commit 61df4b0

Browse files
authored
Merge pull request #47 from common-workflow-language/expr_escaping
expr: better escaping
2 parents ca085e0 + 3c0326c commit 61df4b0

13 files changed

+387
-343
lines changed

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ MODULE=cwl_utils
2626
# `[[` conditional expressions.
2727
PYSOURCES=$(filter-out cwl_utils/parser_v%,$(wildcard ${MODULE}/**.py tests/*.py)) setup.py
2828
DEVPKGS=diff_cover black pylint coverage pep257 pydocstyle flake8 mypy\
29-
isort wheel
29+
isort wheel autoflake
3030
DEBDEVPKGS=pep8 python-autopep8 pylint python-coverage pydocstyle sloccount \
3131
python-flake8 python-mock shellcheck
3232
VERSION=$(shell awk '{print $3}' < cwl_utils/__meta__.py )
@@ -81,6 +81,9 @@ clean: FORCE
8181
sort_imports: $(PYSOURCES)
8282
isort $^
8383

84+
remove_unused_imports: $(PYSOURCES)
85+
autoflake --in-place --remove-all-unused-imports $^
86+
8487
pep257: pydocstyle
8588
## pydocstyle : check Python code style
8689
pydocstyle: $(PYSOURCES)

cwl_utils/cwl_expression_refactor.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#!/usr/bin/env python3
2+
"""CWL Expression refactoring tool for CWL."""
3+
import argparse
4+
import logging
5+
import shutil
6+
import sys
7+
from pathlib import Path
8+
from typing import Any, Callable, Dict, List, MutableSequence, Optional, Tuple, Union
9+
10+
from cwltool.loghandler import _logger as _cwltoollogger
11+
from ruamel import yaml
12+
from typing_extensions import Protocol
13+
14+
_logger = logging.getLogger("cwl-expression-refactor") # pylint: disable=invalid-name
15+
defaultStreamHandler = logging.StreamHandler() # pylint: disable=invalid-name
16+
_logger.addHandler(defaultStreamHandler)
17+
_logger.setLevel(logging.INFO)
18+
_cwltoollogger.setLevel(100)
19+
20+
from cwl_utils import (
21+
cwl_v1_0_expression_refactor,
22+
cwl_v1_1_expression_refactor,
23+
cwl_v1_2_expression_refactor,
24+
parser_v1_0,
25+
parser_v1_1,
26+
parser_v1_2,
27+
)
28+
29+
save_type = Union[Dict[str, str], List[Union[Dict[str, str], List[Any], None]], None]
30+
31+
32+
class saveCWL(Protocol):
33+
"""Shortcut type for CWL v1.x parse.save()."""
34+
35+
def __call__(
36+
self,
37+
val: Optional[Union[Any, MutableSequence[Any]]],
38+
top: bool = True,
39+
base_url: str = "",
40+
relative_uris: bool = True,
41+
) -> save_type:
42+
"""Must use this instead of a Callable due to the keyword args."""
43+
...
44+
45+
46+
def parse_args(args: List[str]) -> argparse.Namespace:
47+
"""Argument parser."""
48+
parser = argparse.ArgumentParser(
49+
description="Tool to refactor CWL documents so that any CWL expression "
50+
"are separate steps as either ExpressionTools or CommandLineTools. Exit code 7 "
51+
"means a single CWL document was provided but it did not need modification."
52+
)
53+
parser.add_argument(
54+
"--etools",
55+
help="Output ExpressionTools, don't go all the way to CommandLineTools.",
56+
action="store_true",
57+
)
58+
parser.add_argument(
59+
"--skip-some1",
60+
help="Don't process CommandLineTool.inputs.inputBinding and CommandLineTool.arguments sections.",
61+
action="store_true",
62+
)
63+
parser.add_argument(
64+
"--skip-some2",
65+
help="Don't process CommandLineTool.outputEval or CommandLineTool.requirements.InitialWorkDirRequirement.",
66+
action="store_true",
67+
)
68+
parser.add_argument("dir", help="Directory in which to save converted files")
69+
parser.add_argument(
70+
"inputs",
71+
nargs="+",
72+
help="One or more CWL documents.",
73+
)
74+
return parser.parse_args(args)
75+
76+
77+
def main(args: Optional[List[str]] = None) -> int:
78+
"""Collect the arguments and run."""
79+
if not args:
80+
args = sys.argv[1:]
81+
return run(parse_args(args))
82+
83+
84+
def run(args: argparse.Namespace) -> int:
85+
"""Primary processing loop."""
86+
for document in args.inputs:
87+
_logger.info("Processing %s.", document)
88+
with open(document, "r") as doc_handle:
89+
result = yaml.main.round_trip_load(doc_handle, preserve_quotes=True)
90+
version = result["cwlVersion"]
91+
uri = Path(document).as_uri()
92+
if version == "v1.0":
93+
top = parser_v1_0.load_document_by_yaml(result, uri)
94+
traverse: Callable[
95+
[Any, bool, bool, bool, bool], Tuple[Any, bool]
96+
] = cwl_v1_0_expression_refactor.traverse
97+
save: saveCWL = parser_v1_0.save
98+
elif version == "v1.1":
99+
top = parser_v1_1.load_document_by_yaml(result, uri)
100+
traverse = cwl_v1_1_expression_refactor.traverse
101+
save = parser_v1_1.save
102+
elif version == "v1.2":
103+
top = parser_v1_2.load_document_by_yaml(result, uri)
104+
traverse = cwl_v1_2_expression_refactor.traverse
105+
save = parser_v1_2.save
106+
else:
107+
_logger.error(
108+
"Sorry, %s is not a supported CWL version by this tool.", version
109+
)
110+
return -1
111+
result, modified = traverse(
112+
top, not args.etools, False, args.skip_some1, args.skip_some2
113+
)
114+
output = Path(args.dir) / Path(document).name
115+
if not modified:
116+
if len(args.inputs) > 1:
117+
shutil.copyfile(document, output)
118+
continue
119+
else:
120+
return 7
121+
if not isinstance(result, MutableSequence):
122+
result_json = save(
123+
result,
124+
base_url=result.loadingOptions.fileuri
125+
if result.loadingOptions.fileuri
126+
else "",
127+
)
128+
# ^^ Setting the base_url and keeping the default value
129+
# for relative_uris=True means that the IDs in the generated
130+
# JSON/YAML are kept clean of the path to the input document
131+
else:
132+
result_json = [
133+
save(result_item, base_url=result_item.loadingOptions.fileuri)
134+
for result_item in result
135+
]
136+
yaml.scalarstring.walk_tree(result_json)
137+
# ^ converts multine line strings to nice multiline YAML
138+
with open(output, "w", encoding="utf-8") as output_filehandle:
139+
output_filehandle.write(
140+
"#!/usr/bin/env cwl-runner\n"
141+
) # TODO: teach the codegen to do this?
142+
yaml.round_trip_dump(result_json, output_filehandle)
143+
return 0
144+
145+
146+
if __name__ == "__main__":
147+
main()
148+
sys.exit(0)

0 commit comments

Comments
 (0)