comparison rgToolFactory.py @ 0:b539dcdc6168 draft

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