1
- from . import docker
2
1
import subprocess
3
2
import json
4
- from .utils import aslist , get_feature
5
3
import logging
6
4
import os
7
- from .errors import WorkflowException
5
+ import re
6
+
7
+ from typing import Any , AnyStr , Union , Text , Dict , List
8
8
import schema_salad .validate as validate
9
9
import schema_salad .ref_resolver
10
+
11
+ from .utils import aslist , get_feature
12
+ from .errors import WorkflowException
10
13
from . import sandboxjs
11
- import re
12
- from typing import Any , AnyStr , Union , Text
14
+ from . import docker
13
15
14
16
_logger = logging .getLogger ("cwltool" )
15
17
16
18
def jshead (engineConfig , rootvars ):
17
19
# type: (List[Text], Dict[Text, Any]) -> Text
18
20
return u"\n " .join (engineConfig + [u"var %s = %s;" % (k , json .dumps (v , indent = 4 )) for k , v in rootvars .items ()])
19
21
20
- def exeval (ex , jobinput , requirements , outdir , tmpdir , context , pull_image ):
21
- # type: (Dict[Text, Any], Dict[Text, Union[Dict, List, Text]], List[Dict[Text, Any]], Text, Text, Any, bool) -> sandboxjs.JSON
22
-
23
- if ex ["engine" ] == "https://w3id.org/cwl/cwl#JavascriptEngine" :
24
- engineConfig = [] # type: List[Text]
25
- for r in reversed (requirements ):
26
- if r ["class" ] == "ExpressionEngineRequirement" and r ["id" ] == "https://w3id.org/cwl/cwl#JavascriptEngine" :
27
- engineConfig = r .get ("engineConfig" , [])
28
- break
29
- rootvars = {
30
- u"inputs" : jobinput ,
31
- u"self" : context ,
32
- u"runtime" : {
33
- u"tmpdir" : tmpdir ,
34
- u"outdir" : outdir }
35
- }
36
- return sandboxjs .execjs (ex ["script" ], jshead (engineConfig , rootvars ))
37
-
38
- for r in reversed (requirements ):
39
- if r ["class" ] == "ExpressionEngineRequirement" and r ["id" ] == ex ["engine" ]:
40
- runtime = [] # type: List[str]
41
-
42
- class DR (object ):
43
- def __init__ (self ): # type: () -> None
44
- self .requirements = None # type: List[None]
45
- self .hints = None # type: List[None]
46
- dr = DR ()
47
- dr .requirements = r .get ("requirements" , [])
48
- dr .hints = r .get ("hints" , [])
49
-
50
- (docker_req , docker_is_req ) = get_feature (dr , "DockerRequirement" )
51
- img_id = None
52
- if docker_req :
53
- img_id = docker .get_from_requirements (docker_req , docker_is_req , pull_image )
54
- if img_id :
55
- runtime = ["docker" , "run" , "-i" , "--rm" , str (img_id )]
56
-
57
- inp = {
58
- "script" : ex ["script" ],
59
- "engineConfig" : r .get ("engineConfig" , []),
60
- "job" : jobinput ,
61
- "context" : context ,
62
- "outdir" : outdir ,
63
- "tmpdir" : tmpdir ,
64
- }
65
-
66
- _logger .debug (u"Invoking expression engine %s with %s" ,
67
- runtime + aslist (r ["engineCommand" ]),
68
- json .dumps (inp , indent = 4 ))
69
-
70
- sp = subprocess .Popen (runtime + aslist (r ["engineCommand" ]),
71
- shell = False ,
72
- close_fds = True ,
73
- stdin = subprocess .PIPE ,
74
- stdout = subprocess .PIPE )
75
-
76
- (stdoutdata , stderrdata ) = sp .communicate (json .dumps (inp ) + "\n \n " )
77
- if sp .returncode != 0 :
78
- raise WorkflowException (u"Expression engine returned non-zero exit code on evaluation of\n %s" % json .dumps (inp , indent = 4 ))
79
-
80
- return json .loads (stdoutdata )
81
-
82
- raise WorkflowException (u"Unknown expression engine '%s'" % ex ["engine" ])
83
-
84
22
seg_symbol = r"""\w+"""
85
23
seg_single = r"""\['([^']|\\')+'\]"""
86
24
seg_double = r"""\["([^"]|\\")+"\]"""
87
25
seg_index = r"""\[[0-9]+\]"""
88
26
segments = r"(\.%s|%s|%s|%s)" % (seg_symbol , seg_single , seg_double , seg_index )
89
27
segment_re = re .compile (segments , flags = re .UNICODE )
90
- param_re = re .compile (r"\$\((%s)%s*\)" % (seg_symbol , segments ), flags = re .UNICODE )
28
+ param_re = re .compile (r"\((%s)%s*\)$" % (seg_symbol , segments ), flags = re .UNICODE )
29
+
30
+ JSON = Union [Dict [Any ,Any ], List [Any ], Text , int , long , float , bool , None ]
31
+
32
+ class SubstitutionError (Exception ):
33
+ pass
34
+
35
+ def scanner (scan ): # type: (Text) -> List[int]
36
+ DEFAULT = 0
37
+ DOLLAR = 1
38
+ PAREN = 2
39
+ BRACE = 3
40
+ SINGLE_QUOTE = 4
41
+ DOUBLE_QUOTE = 5
42
+ BACKSLASH = 6
43
+
44
+ i = 0
45
+ stack = [DEFAULT ]
46
+ start = 0
47
+ while i < len (scan ):
48
+ state = stack [- 1 ]
49
+ c = scan [i ]
50
+
51
+ if state == DEFAULT :
52
+ if c == '$' :
53
+ stack .append (DOLLAR )
54
+ elif c == '\\ ' :
55
+ stack .append (BACKSLASH )
56
+ elif state == BACKSLASH :
57
+ stack .pop ()
58
+ if stack [- 1 ] == DEFAULT :
59
+ return [i - 1 , i + 1 ]
60
+ elif state == DOLLAR :
61
+ if c == '(' :
62
+ start = i - 1
63
+ stack .append (PAREN )
64
+ elif c == '{' :
65
+ start = i - 1
66
+ stack .append (BRACE )
67
+ else :
68
+ stack .pop ()
69
+ elif state == PAREN :
70
+ if c == '(' :
71
+ stack .append (PAREN )
72
+ elif c == ')' :
73
+ stack .pop ()
74
+ if stack [- 1 ] == DOLLAR :
75
+ return [start , i + 1 ]
76
+ elif c == "'" :
77
+ stack .append (SINGLE_QUOTE )
78
+ elif c == '"' :
79
+ stack .append (DOUBLE_QUOTE )
80
+ elif state == BRACE :
81
+ if c == '{' :
82
+ stack .append (BRACE )
83
+ elif c == '}' :
84
+ stack .pop ()
85
+ if stack [- 1 ] == DOLLAR :
86
+ return [start , i + 1 ]
87
+ elif c == "'" :
88
+ stack .append (SINGLE_QUOTE )
89
+ elif c == '"' :
90
+ stack .append (DOUBLE_QUOTE )
91
+ elif state == SINGLE_QUOTE :
92
+ if c == "'" :
93
+ stack .pop ()
94
+ elif c == '\\ ' :
95
+ stack .append (BACKSLASH )
96
+ elif state == DOUBLE_QUOTE :
97
+ if c == '"' :
98
+ stack .pop ()
99
+ elif c == '\\ ' :
100
+ stack .append (BACKSLASH )
101
+ i += 1
102
+
103
+ if len (stack ) > 1 :
104
+ raise SubstitutionError ("Substitution error, unfinished block starting at position {}: {}" .format (start , scan [start :]))
105
+ else :
106
+ return None
91
107
92
108
def next_seg (remain , obj ): # type: (Text, Any)->Text
93
109
if remain :
@@ -103,24 +119,42 @@ def next_seg(remain, obj): # type: (Text, Any)->Text
103
119
else :
104
120
return obj
105
121
106
-
107
- def param_interpolate (ex , obj , strip = True ):
108
- # type: (Text, Dict[Any, Any], bool) -> Union[Text, Text]
109
- m = param_re .search (ex )
122
+ def evaluator (ex , jslib , obj , fullJS = False , timeout = None ):
123
+ # type: (Text, Text, Dict[Text, Any], bool, int) -> JSON
124
+ m = param_re .match (ex )
110
125
if m :
111
- leaf = next_seg (m .group (0 )[m .end (1 ) - m .start (0 ):- 1 ], obj [m .group (1 )])
112
- if strip and len (ex .strip ()) == len (m .group (0 )):
113
- return leaf
114
- else :
115
- leaf = json .dumps (leaf , sort_keys = True )
126
+ return next_seg (m .group (0 )[m .end (1 ) - m .start (0 ):- 1 ], obj [m .group (1 )])
127
+ elif fullJS :
128
+ return sandboxjs .execjs (ex , jslib , timeout = timeout )
129
+ else :
130
+ raise sandboxjs .JavascriptException ("Syntax error in parameter reference '%s' or used Javascript code without specifying InlineJavascriptRequirement." , ex )
131
+
132
+ def interpolate (scan , rootvars ,
133
+ timeout = None , fullJS = None , jslib = "" ):
134
+ # type: (Text, Dict[Text, Any], int, bool, Union[str, Text]) -> JSON
135
+ scan = scan .strip ()
136
+ parts = []
137
+ w = scanner (scan )
138
+ while w :
139
+ parts .append (scan [0 :w [0 ]])
140
+
141
+ if scan [w [0 ]] == '$' :
142
+ e = evaluator (scan [w [0 ]+ 1 :w [1 ]], jslib , rootvars , fullJS = fullJS ,
143
+ timeout = timeout )
144
+ if w [0 ] == 0 and w [1 ] == len (scan ):
145
+ return e
146
+ leaf = json .dumps (e , sort_keys = True )
116
147
if leaf [0 ] == '"' :
117
148
leaf = leaf [1 :- 1 ]
118
- return ex [0 :m .start (0 )] + leaf + param_interpolate (ex [m .end (0 ):], obj , False )
119
- else :
120
- if "$(" in ex or "${" in ex :
121
- _logger .warn (u"Warning possible workflow bug: found '$(' or '${' in '%s' but did not match valid parameter reference and InlineJavascriptRequirement not specified." , ex )
122
- return ex
149
+ parts .append (leaf )
150
+ elif scan [w [0 ]] == '\\ ' :
151
+ e = scan [w [1 ]- 1 ]
152
+ parts .append (e )
123
153
154
+ scan = scan [w [1 ]:]
155
+ w = scanner (scan )
156
+ parts .append (scan )
157
+ return '' .join (parts )
124
158
125
159
def do_eval (ex , jobinput , requirements , outdir , tmpdir , resources ,
126
160
context = None , pull_image = True , timeout = None ):
@@ -135,13 +169,19 @@ def do_eval(ex, jobinput, requirements, outdir, tmpdir, resources,
135
169
u"self" : context ,
136
170
u"runtime" : runtime }
137
171
138
- if isinstance (ex , dict ) and "engine" in ex and "script" in ex :
139
- return exeval (ex , jobinput , requirements , outdir , tmpdir , context , pull_image )
140
172
if isinstance (ex , (str , Text )):
173
+ fullJS = False
174
+ jslib = u""
141
175
for r in reversed (requirements ):
142
176
if r ["class" ] == "InlineJavascriptRequirement" :
143
- return sandboxjs .interpolate (Text (ex ), jshead (
144
- r .get ("expressionLib" , []), rootvars ), timeout = timeout )
145
- return param_interpolate (Text (ex ), rootvars )
177
+ fullJS = True
178
+ jslib = jshead (r .get ("expressionLib" , []), rootvars )
179
+ break
180
+
181
+ return interpolate (ex ,
182
+ rootvars ,
183
+ timeout = timeout ,
184
+ fullJS = fullJS ,
185
+ jslib = jslib )
146
186
else :
147
187
return ex
0 commit comments