comparison rgToolFactory.py @ 53:b608991544f5 draft

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