comparison rgToolFactory.py @ 36:b8f9b2c64202 draft

Uploaded
author fubar
date Sun, 22 Dec 2013 01:48:38 -0500
parents 8051d14cf819
children
comparison
equal deleted inserted replaced
35:375269b30baf 36:b8f9b2c64202
1 # rgToolFactory.py
2 # see https://bitbucket.org/fubar/galaxytoolfactory/wiki/Home
3 #
4 # copyright ross lazarus (ross stop lazarus at gmail stop com) May 2012
5 #
6 # all rights reserved
7 # Licensed under the LGPL
8 # suggestions for improvement and bug fixes welcome at https://bitbucket.org/fubar/galaxytoolfactory/wiki/Home
9 #
10 # January 2013
11 # problem pointed out by Carlos Borroto
12 # added escaping for <>$ - thought I did that ages ago...
13 #
14 # August 11 2012
15 # changed to use shell=False and cl as a sequence
16
17 # This is a Galaxy tool factory for simple scripts in python, R or whatever ails ye.
18 # It also serves as the wrapper for the new tool.
19 #
20 # you paste and run your script
21 # Only works for simple scripts that read one input from the history.
22 # Optionally can write one new history dataset,
23 # and optionally collect any number of outputs into links on an autogenerated HTML page.
24
25 # DO NOT install on a public or important site - please.
26
27 # installed generated tools are fine if the script is safe.
28 # They just run normally and their user cannot do anything unusually insecure
29 # but please, practice safe toolshed.
30 # Read the fucking code before you install any tool
31 # especially this one
32
33 # After you get the script working on some test data, you can
34 # optionally generate a toolshed compatible gzip file
35 # containing your script safely wrapped as an ordinary Galaxy script in your local toolshed for
36 # safe and largely automated installation in a production Galaxy.
37
38 # If you opt for an HTML output, you get all the script outputs arranged
39 # as a single Html history item - all output files are linked, thumbnails for all the pdfs.
40 # Ugly but really inexpensive.
41 #
42 # Patches appreciated please.
43 #
44 #
45 # long route to June 2012 product
46 # Behold the awesome power of Galaxy and the toolshed with the tool factory to bind them
47 # derived from an integrated script model
48 # called rgBaseScriptWrapper.py
49 # Note to the unwary:
50 # This tool allows arbitrary scripting on your Galaxy as the Galaxy user
51 # There is nothing stopping a malicious user doing whatever they choose
52 # Extremely dangerous!!
53 # Totally insecure. So, trusted users only
54 #
55 # preferred model is a developer using their throw away workstation instance - ie a private site.
56 # no real risk. The universe_wsgi.ini admin_users string is checked - only admin users are permitted to run this tool.
57 #
58
59 import sys
60 import shutil
61 import subprocess
62 import os
63 import time
64 import tempfile
65 import optparse
66 import tarfile
67 import re
68 import shutil
69 import math
70
71 progname = os.path.split(sys.argv[0])[1]
72 myversion = 'V000.2 June 2012'
73 verbose = False
74 debug = False
75 toolFactoryURL = 'https://bitbucket.org/fubar/galaxytoolfactory'
76
77 def timenow():
78 """return current time as a string
79 """
80 return time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(time.time()))
81
82 cheetah_escape_table = {
83 "$": "\$"
84 }
85
86 cheetah_unescape_table = {
87 "\$": "$"
88 }
89
90 def html_escape(t):
91 """Unescape \$ first in case already done
92 cheetah barfs if any $ without \
93 xml parsing is controlled with <![CDATA[...]]>
94 """
95 text = t
96 for k in cheetah_unescape_table.keys():
97 text = text.replace(k,cheetah_unescape_table[k])
98 for k in cheetah_escape_table.keys():
99 text = text.replace(k,cheetah_escape_table[k])
100 return text
101
102
103 class ScriptRunner:
104 """class is a wrapper for an arbitrary script
105 """
106
107 def __init__(self,opts=None,treatbashSpecial=True):
108 """
109 cleanup inputs, setup some outputs
110
111 """
112 self.treatbashSpecial = treatbashSpecial
113 if opts.output_dir: # simplify for the tool tarball
114 os.chdir(opts.output_dir)
115 self.thumbformat = 'jpg'
116 self.opts = opts
117 self.toolname = re.sub('[^a-zA-Z0-9_]+', '', opts.tool_name) # a sanitizer now does this but..
118 self.toolid = self.toolname
119 self.myname = sys.argv[0] # get our name because we write ourselves out as a tool later
120 self.pyfile = self.myname # crude but efficient - the cruft won't hurt much
121 self.xmlfile = '%s.xml' % self.toolname
122 s = open(self.opts.script_path,'r').readlines()
123 s = [x.rstrip() for x in s] # remove pesky dos line endings if needed
124 self.script = '\n'.join(s)
125 fhandle,self.sfile = tempfile.mkstemp(prefix=self.toolname,suffix=".%s" % (opts.interpreter))
126 tscript = open(self.sfile,'w') # use self.sfile as script source for Popen
127 tscript.write(self.script)
128 tscript.close()
129 self.escapedS = [html_escape(x) for x in s] # for restructured text in help
130 self.escapedScript = '\n'.join(self.escapedS)
131 self.indentedScript = '\n'.join([' %s' % x for x in s]) # for restructured text in help
132 if opts.output_dir: # may not want these complexities
133 self.tlog = os.path.join(opts.output_dir,"%s_runner.log" % self.toolname)
134 art = '%s.%s' % (self.toolname,opts.interpreter)
135 artpath = os.path.join(self.opts.output_dir,art) # need full path
136 artifact = open(artpath,'w') # use self.sfile as script source for Popen
137 artifact.write(self.script)
138 artifact.close()
139 self.cl = []
140 self.html = []
141 a = self.cl.append
142 a(opts.interpreter)
143 if self.treatbashSpecial and opts.interpreter in ['bash','sh']:
144 a(self.sfile)
145 else:
146 a('-') # stdin
147 a(opts.input_tab)
148 a(opts.output_tab)
149 self.outFormats = 'tabular' # TODO make this an option at tool generation time
150 self.inputFormats = 'tabular' # TODO make this an option at tool generation time
151 self.test1Input = '%s_test1_input.xls' % self.toolname
152 self.test1Output = '%s_test1_output.xls' % self.toolname
153 self.test1HTML = '%s_test1_output.html' % self.toolname
154
155 def makeXML(self):
156 """
157 Create a Galaxy xml tool wrapper for the new script as a string to write out
158 fixme - use templating or something less fugly than this example of what we produce
159
160 <tool id="reverse" name="reverse" version="0.01">
161 <description>a tabular file</description>
162 <command interpreter="python">
163 reverse.py --script_path "$runMe" --interpreter "python"
164 --tool_name "reverse" --input_tab "$input1" --output_tab "$tab_file"
165 </command>
166 <inputs>
167 <param name="input1" type="data" format="tabular" label="Select a suitable input file from your history"/><param name="job_name" type="text" label="Supply a name for the outputs to remind you what they contain" value="reverse"/>
168
169 </inputs>
170 <outputs>
171 <data format="tabular" name="tab_file" label="${job_name}"/>
172
173 </outputs>
174 <help>
175
176 **What it Does**
177
178 Reverse the columns in a tabular file
179
180 </help>
181 <configfiles>
182 <configfile name="runMe">
183
184 # reverse order of columns in a tabular file
185 import sys
186 inp = sys.argv[1]
187 outp = sys.argv[2]
188 i = open(inp,'r')
189 o = open(outp,'w')
190 for row in i:
191 rs = row.rstrip().split('\t')
192 rs.reverse()
193 o.write('\t'.join(rs))
194 o.write('\n')
195 i.close()
196 o.close()
197
198
199 </configfile>
200 </configfiles>
201 </tool>
202
203 """
204 newXML="""<tool id="%(toolid)s" name="%(toolname)s" version="%(tool_version)s">
205 %(tooldesc)s
206 %(command)s
207 <inputs>
208 %(inputs)s
209 </inputs>
210 <outputs>
211 %(outputs)s
212 </outputs>
213 <configfiles>
214 <configfile name="runMe">
215 <![CDATA[
216 %(script)s
217 ]]>
218 </configfile>
219 </configfiles>
220 %(tooltests)s
221 <help>
222 <![CDATA[
223 %(help)s
224 ]]>
225 </help>
226 </tool>"""
227 # needs a dict with toolname, toolid, interpreter, scriptname, command, inputs as a multi line string ready to write, outputs ditto, help ditto
228
229 newCommand="""<command interpreter="python">
230 %(toolname)s.py --script_path "$runMe" --interpreter "%(interpreter)s"
231 --tool_name "%(toolname)s" %(command_inputs)s %(command_outputs)s
232 </command>""" # may NOT be an input or htmlout
233 tooltestsTabOnly = """<tests><test>
234 <param name="input1" value="%(test1Input)s" ftype="tabular"/>
235 <param name="job_name" value="test1"/>
236 <param name="runMe" value="$runMe"/>
237 <output name="tab_file" file="%(test1Output)s" ftype="tabular"/>
238 </test></tests>"""
239 tooltestsHTMLOnly = """<tests><test>
240 <param name="input1" value="%(test1Input)s" ftype="tabular"/>
241 <param name="job_name" value="test1"/>
242 <param name="runMe" value="$runMe"/>
243 <output name="html_file" file="%(test1HTML)s" ftype="html" lines_diff="5"/>
244 </test></tests>"""
245 tooltestsBoth = """<tests><test>
246 <param name="input1" value="%(test1Input)s" ftype="tabular"/>
247 <param name="job_name" value="test1"/>
248 <param name="runMe" value="$runMe"/>
249 <output name="tab_file" file="%(test1Output)s" ftype="tabular" />
250 <output name="html_file" file="%(test1HTML)s" ftype="html" lines_diff="10"/>
251 </test></tests>"""
252 xdict = {}
253 xdict['tool_version'] = self.opts.tool_version
254 xdict['test1Input'] = self.test1Input
255 xdict['test1HTML'] = self.test1HTML
256 xdict['test1Output'] = self.test1Output
257 if self.opts.make_HTML and self.opts.output_tab <> 'None':
258 xdict['tooltests'] = tooltestsBoth % xdict
259 elif self.opts.make_HTML:
260 xdict['tooltests'] = tooltestsHTMLOnly % xdict
261 else:
262 xdict['tooltests'] = tooltestsTabOnly % xdict
263 xdict['script'] = self.escapedScript
264 # configfile is least painful way to embed script to avoid external dependencies
265 # but requires escaping of <, > and $ to avoid Mako parsing
266 if self.opts.help_text:
267 xdict['help'] = open(self.opts.help_text,'r').read()
268 else:
269 xdict['help'] = 'Please ask the tool author for help as none was supplied at tool generation'
270 coda = ['**Script**','Pressing execute will run the following code over your input file and generate some outputs in your history::\n']
271 coda.append(self.indentedScript)
272 coda.append('\n\n')
273 coda.append('**Attribution** This Galaxy tool was created by %s at %s\nusing the Galaxy Tool Factory.' % (self.opts.user_email,timenow()))
274 coda.append('See %s for details of that project' % (toolFactoryURL))
275 coda.append('Please cite: Creating re-usable tools from scripts: The Galaxy Tool Factory. Ross Lazarus; Antony Kaspi; Mark Ziemann; The Galaxy Team. ')
276 coda.append('Bioinformatics 2012; doi: 10.1093/bioinformatics/bts573')
277 xdict['help'] = '%s\n%s' % (xdict['help'],'\n'.join(coda))
278 if self.opts.tool_desc:
279 xdict['tooldesc'] = '<description>%s</description>' % self.opts.tool_desc
280 else:
281 xdict['tooldesc'] = ''
282 xdict['command_outputs'] = ''
283 xdict['outputs'] = ''
284 if self.opts.input_tab <> 'None':
285 xdict['command_inputs'] = '--input_tab "$input1" ' # the space may matter a lot if we append something
286 xdict['inputs'] = '<param name="input1" type="data" format="%s" label="Select a suitable input file from your history"/> \n' % self.inputFormats
287 else:
288 xdict['command_inputs'] = '' # assume no input - eg a random data generator
289 xdict['inputs'] = ''
290 xdict['inputs'] += '<param name="job_name" type="text" label="Supply a name for the outputs to remind you what they contain" value="%s"/> \n' % self.toolname
291 xdict['toolname'] = self.toolname
292 xdict['toolid'] = self.toolid
293 xdict['interpreter'] = self.opts.interpreter
294 xdict['scriptname'] = self.sfile
295 if self.opts.make_HTML:
296 xdict['command_outputs'] += ' --output_dir "$html_file.files_path" --output_html "$html_file" --make_HTML "yes" '
297 xdict['outputs'] += ' <data format="html" name="html_file" label="${job_name}.html"/>\n'
298 if self.opts.output_tab <> 'None':
299 xdict['command_outputs'] += ' --output_tab "$tab_file"'
300 xdict['outputs'] += ' <data format="%s" name="tab_file" label="${job_name}"/>\n' % self.outFormats
301 xdict['command'] = newCommand % xdict
302 xmls = newXML % xdict
303 xf = open(self.xmlfile,'w')
304 xf.write(xmls)
305 xf.write('\n')
306 xf.close()
307 # ready for the tarball
308
309
310 def makeTooltar(self):
311 """
312 a tool is a gz tarball with eg
313 /toolname/tool.xml /toolname/tool.py /toolname/test-data/test1_in.foo ...
314 """
315 retval = self.run()
316 if retval:
317 print >> sys.stderr,'## Run failed. Cannot build yet. Please fix and retry'
318 sys.exit(1)
319 self.makeXML()
320 tdir = self.toolname
321 os.mkdir(tdir)
322 if self.opts.input_tab <> 'None': # no reproducible test otherwise? TODO: maybe..
323 testdir = os.path.join(tdir,'test-data')
324 os.mkdir(testdir) # make tests directory
325 shutil.copyfile(self.opts.input_tab,os.path.join(testdir,self.test1Input))
326 if self.opts.output_tab <> 'None':
327 shutil.copyfile(self.opts.output_tab,os.path.join(testdir,self.test1Output))
328 if self.opts.make_HTML:
329 shutil.copyfile(self.opts.output_html,os.path.join(testdir,self.test1HTML))
330 if self.opts.output_dir:
331 shutil.copyfile(self.tlog,os.path.join(testdir,'test1_out.log'))
332 op = '%s.py' % self.toolname # new name
333 outpiname = os.path.join(tdir,op) # path for the tool tarball
334 pyin = os.path.basename(self.pyfile) # our name - we rewrite ourselves (TM)
335 notes = ['# %s - a self annotated version of %s generated by running %s\n' % (op,pyin,pyin),]
336 notes.append('# to make a new Galaxy tool called %s\n' % self.toolname)
337 notes.append('# User %s at %s\n' % (self.opts.user_email,timenow()))
338 pi = open(self.pyfile,'r').readlines() # our code becomes new tool wrapper (!) - first Galaxy worm
339 notes += pi
340 outpi = open(outpiname,'w')
341 outpi.write(''.join(notes))
342 outpi.write('\n')
343 outpi.close()
344 stname = os.path.join(tdir,self.sfile)
345 if not os.path.exists(stname):
346 shutil.copyfile(self.sfile, stname)
347 xtname = os.path.join(tdir,self.xmlfile)
348 if not os.path.exists(xtname):
349 shutil.copyfile(self.xmlfile,xtname)
350 tarpath = "%s.gz" % self.toolname
351 tar = tarfile.open(tarpath, "w:gz")
352 tar.add(tdir,arcname=self.toolname)
353 tar.close()
354 shutil.copyfile(tarpath,self.opts.new_tool)
355 shutil.rmtree(tdir)
356 ## TODO: replace with optional direct upload to local toolshed?
357 return retval
358
359 def compressPDF(self,inpdf=None,thumbformat='png'):
360 """need absolute path to pdf
361 """
362 assert os.path.isfile(inpdf), "## Input %s supplied to %s compressPDF not found" % (inpdf,self.myName)
363 hf,hlog = tempfile.mkstemp(suffix="%s.log" % self.toolname)
364 sto = open(hlog,'w')
365 outpdf = '%s_compressed' % inpdf
366 cl = ["gs", "-sDEVICE=pdfwrite", "-dNOPAUSE", "-dBATCH", "-sOutputFile=%s" % outpdf,inpdf]
367 x = subprocess.Popen(cl,stdout=sto,stderr=sto,cwd=self.opts.output_dir)
368 retval1 = x.wait()
369 if retval1 == 0:
370 os.unlink(inpdf)
371 shutil.move(outpdf,inpdf)
372 outpng = '%s.%s' % (os.path.splitext(inpdf)[0],thumbformat)
373 cl2 = ['convert', inpdf, outpng]
374 x = subprocess.Popen(cl2,stdout=sto,stderr=sto,cwd=self.opts.output_dir)
375 retval2 = x.wait()
376 sto.close()
377 retval = retval1 or retval2
378 return retval
379
380
381 def getfSize(self,fpath,outpath):
382 """
383 format a nice file size string
384 """
385 size = ''
386 fp = os.path.join(outpath,fpath)
387 if os.path.isfile(fp):
388 size = '0 B'
389 n = float(os.path.getsize(fp))
390 if n > 2**20:
391 size = '%1.1f MB' % (n/2**20)
392 elif n > 2**10:
393 size = '%1.1f KB)' % (n/2**10)
394 elif n > 0:
395 size = '%d B' % (int(n))
396 return size
397
398 def makeHtml(self):
399 """ Create an HTML file content to list all the artifacts found in the output_dir
400 """
401
402 galhtmlprefix = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
403 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
404 <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
405 <meta name="generator" content="Galaxy %s tool output - see http://g2.trac.bx.psu.edu/" />
406 <title></title>
407 <link rel="stylesheet" href="/static/style/base.css" type="text/css" />
408 </head>
409 <body>
410 <div class="toolFormBody">
411 """
412 galhtmlattr = """<hr/><div class="infomessage">This tool (%s) was generated by the <a href="https://bitbucket.org/fubar/galaxytoolfactory/overview">Galaxy Tool Factory</a></div><br/>"""
413 galhtmlpostfix = """</div></body></html>\n"""
414
415 flist = os.listdir(self.opts.output_dir)
416 flist = [x for x in flist if x <> 'Rplots.pdf']
417 flist.sort()
418 html = []
419 html.append(galhtmlprefix % progname)
420 html.append('<div class="infomessage">Galaxy Tool "%s" run at %s</div><br/>' % (self.toolname,timenow()))
421 fhtml = []
422 if len(flist) > 0:
423 pdflist = []
424 npdf = len([x for x in flist if os.path.splitext(x)[-1].lower() == '.pdf'])
425 nacross = 1
426 if npdf > 0:
427 nacross = int(round(math.log(npdf,2)))
428 nacross = max(1,nacross)
429 width = min(400,int(1200/nacross))
430 for rownum,fname in enumerate(flist):
431 dname,e = os.path.splitext(fname)
432 sfsize = self.getfSize(fname,self.opts.output_dir)
433 if e.lower() == '.pdf' : # compress and make a thumbnail
434 thumb = '%s.%s' % (dname,self.thumbformat)
435 pdff = os.path.join(self.opts.output_dir,fname)
436 retval = self.compressPDF(inpdf=pdff,thumbformat=self.thumbformat)
437 if retval == 0:
438 pdflist.append((fname,thumb))
439 if (rownum+1) % 2 == 0:
440 fhtml.append('<tr class="odd_row"><td><a href="%s">%s</a></td><td>%s</td></tr>' % (fname,fname,sfsize))
441 else:
442 fhtml.append('<tr><td><a href="%s">%s</a></td><td>%s</td></tr>' % (fname,fname,sfsize))
443 ntogo = nacross # counter for table row padding with empty cells
444 if len(pdflist) > 0:
445 html.append('<div><table class="simple" cellpadding="2" cellspacing="2">\n<tr>')
446 for i,paths in enumerate(pdflist):
447 fname,thumb = paths
448 s= """<td><a href="%s"><img src="%s" title="Click to download a PDF of %s" hspace="5" width="%d"
449 alt="Image called %s"/></a></td>\n""" % (fname,thumb,fname,width,fname)
450 if ((i+1) % nacross == 0):
451 s += '</tr>\n'
452 ntogo = 0
453 if i < (npdf - 1): # more to come
454 s += '<tr>'
455 ntogo = nacross
456 else:
457 ntogo -= 1
458 html.append(s)
459 if html[-1].strip().endswith('</tr>'):
460 html.append('</table></div>\n')
461 else:
462 if ntogo > 0: # pad
463 html.append('<td>&nbsp;</td>'*ntogo)
464 html.append('</tr></table></div>\n')
465 if len(fhtml) > 0:
466 fhtml.insert(0,'<div><table class="colored" cellpadding="3" cellspacing="3"><tr><th>Output File Name (click to view)</th><th>Size</th></tr>\n')
467 fhtml.append('</table></div><br/>')
468 html += fhtml # add all non-pdf files to the end of the display
469 else:
470 html.append('<div class="warningmessagelarge">### Error - %s returned no files - please confirm that parameters are sane</div>' % self.opts.interpreter)
471 rlog = open(self.tlog,'r').readlines()
472 rlog = [x for x in rlog if x.strip() > '']
473 if len(rlog) > 1:
474 html.append('<div class="toolFormTitle">%s log</div><pre>\n' % self.opts.interpreter)
475 html += rlog
476 html.append('</pre>\n')
477 html.append(galhtmlattr % (self.toolname))
478 html.append(galhtmlpostfix)
479 htmlf = file(self.opts.output_html,'w')
480 htmlf.write('\n'.join(html))
481 htmlf.write('\n')
482 htmlf.close()
483 self.html = html
484
485
486 def run(self):
487 """
488 scripts must be small enough not to fill the pipe!
489 """
490 if self.treatbashSpecial and self.opts.interpreter in ['bash','sh']:
491 retval = self.runBash()
492 else:
493 if self.opts.output_dir:
494 sto = open(self.tlog,'w')
495 sto.write('## Toolfactory generated command line = %s\n' % ' '.join(self.cl))
496 sto.flush()
497 p = subprocess.Popen(self.cl,shell=False,stdout=sto,stderr=sto,stdin=subprocess.PIPE,cwd=self.opts.output_dir)
498 else:
499 p = subprocess.Popen(self.cl,shell=False,stdin=subprocess.PIPE)
500 p.stdin.write(self.script)
501 p.stdin.close()
502 retval = p.wait()
503 if self.opts.output_dir:
504 sto.close()
505 if self.opts.make_HTML:
506 self.makeHtml()
507 return retval
508
509 def runBash(self):
510 """
511 cannot use - for bash so use self.sfile
512 """
513 if self.opts.output_dir:
514 s = '## Toolfactory generated command line = %s\n' % ' '.join(self.cl)
515 sto = open(self.tlog,'w')
516 sto.write(s)
517 sto.flush()
518 p = subprocess.Popen(self.cl,shell=False,stdout=sto,stderr=sto,cwd=self.opts.output_dir)
519 else:
520 p = subprocess.Popen(self.cl,shell=False)
521 retval = p.wait()
522 if self.opts.output_dir:
523 sto.close()
524 if self.opts.make_HTML:
525 self.makeHtml()
526 return retval
527
528
529 def main():
530 u = """
531 This is a Galaxy wrapper. It expects to be called by a special purpose tool.xml as:
532 <command interpreter="python">rgBaseScriptWrapper.py --script_path "$scriptPath" --tool_name "foo" --interpreter "Rscript"
533 </command>
534 """
535 op = optparse.OptionParser()
536 a = op.add_option
537 a('--script_path',default=None)
538 a('--tool_name',default=None)
539 a('--interpreter',default=None)
540 a('--output_dir',default=None)
541 a('--output_html',default=None)
542 a('--input_tab',default="None")
543 a('--output_tab',default="None")
544 a('--user_email',default='Unknown')
545 a('--bad_user',default=None)
546 a('--make_Tool',default=None)
547 a('--make_HTML',default=None)
548 a('--help_text',default=None)
549 a('--tool_desc',default=None)
550 a('--new_tool',default=None)
551 a('--tool_version',default=None)
552 opts, args = op.parse_args()
553 assert not opts.bad_user,'UNAUTHORISED: %s is NOT authorized to use this tool until Galaxy admin adds %s to admin_users in universe_wsgi.ini' % (opts.bad_user,opts.bad_user)
554 assert opts.tool_name,'## Tool Factory expects a tool name - eg --tool_name=DESeq'
555 assert opts.interpreter,'## Tool Factory wrapper expects an interpreter - eg --interpreter=Rscript'
556 assert os.path.isfile(opts.script_path),'## Tool Factory wrapper expects a script path - eg --script_path=foo.R'
557 if opts.output_dir:
558 try:
559 os.makedirs(opts.output_dir)
560 except:
561 pass
562 r = ScriptRunner(opts)
563 if opts.make_Tool:
564 retcode = r.makeTooltar()
565 else:
566 retcode = r.run()
567 os.unlink(r.sfile)
568 if retcode:
569 sys.exit(retcode) # indicate failure to job runner
570
571
572 if __name__ == "__main__":
573 main()
574
575