comparison rgToolFactory.py @ 4:fb4959ed5b2b draft

Fixes to paths in git for deps
author fubar
date Sat, 31 Oct 2015 02:26:24 -0400
parents cf295f36d606
children
comparison
equal deleted inserted replaced
3:01a5391488cd 4:fb4959ed5b2b
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):
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 if opts.output_dir: # simplify for the tool tarball
126 os.chdir(opts.output_dir)
127 self.thumbformat = 'png'
128 self.opts = opts
129 self.toolname = re.sub('[^a-zA-Z0-9_]+', '', opts.tool_name) # a sanitizer now does this but..
130 self.toolid = self.toolname
131 self.myname = sys.argv[0] # get our name because we write ourselves out as a tool later
132 self.pyfile = self.myname # crude but efficient - the cruft won't hurt much
133 self.xmlfile = '%s.xml' % self.toolname
134 s = open(self.opts.script_path,'r').readlines()
135 s = [x.rstrip() for x in s] # remove pesky dos line endings if needed
136 self.script = '\n'.join(s)
137 fhandle,self.sfile = tempfile.mkstemp(prefix=self.toolname,suffix=".%s" % (opts.interpreter))
138 tscript = open(self.sfile,'w') # use self.sfile as script source for Popen
139 tscript.write(self.script)
140 tscript.close()
141 self.indentedScript = '\n'.join([' %s' % x for x in s]) # for restructured text in help
142 self.escapedScript = '\n'.join([html_escape(x) for x in s])
143 self.elog = os.path.join(self.opts.output_dir,"%s_error.log" % self.toolname)
144 self.tlog = os.path.join(self.opts.output_dir,"%s_runner.log" % self.toolname)
145 if opts.output_dir: # may not want these complexities
146 art = '%s.%s' % (self.toolname,opts.interpreter)
147 artpath = os.path.join(self.opts.output_dir,art) # need full path
148 artifact = open(artpath,'w') # use self.sfile as script source for Popen
149 artifact.write(self.script)
150 artifact.close()
151 self.html = []
152 self.cl = (opts.interpreter,self.sfile)
153 self.outFormats = 'tabular' # TODO make this an option at tool generation time
154 self.inputFormats = 'tabular' # TODO make this an option at tool generation time
155 self.test1Input = '%s_test1_input.xls' % self.toolname
156 self.test1Output = '%s_test1_output.xls' % self.toolname
157 self.test1HTML = '%s_test1_output.html' % self.toolname
158
159 def makeXML(self):
160 """
161 Create a Galaxy xml tool wrapper for the new script as a string to write out
162 fixme - use templating or something less fugly than this example of what we produce
163
164 <tool id="reverse" name="reverse" version="0.01">
165 <description>a tabular file</description>
166 <command interpreter="python">
167 reverse.py --script_path "$runMe" --interpreter "python"
168 --tool_name "reverse" --input_tab "$input1" --output_tab "$tab_file"
169 </command>
170 <inputs>
171 <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"/>
172
173 </inputs>
174 <outputs>
175 <data format="tabular" name="tab_file" label="${job_name}"/>
176
177 </outputs>
178 <help>
179
180 **What it Does**
181
182 Reverse the columns in a tabular file
183
184 </help>
185 <configfiles>
186 <configfile name="runMe">
187
188 # reverse order of columns in a tabular file
189 import sys
190 inp = sys.argv[1]
191 outp = sys.argv[2]
192 i = open(inp,'r')
193 o = open(outp,'w')
194 for row in i:
195 rs = row.rstrip().split('\t')
196 rs.reverse()
197 o.write('\t'.join(rs))
198 o.write('\n')
199 i.close()
200 o.close()
201
202
203 </configfile>
204 </configfiles>
205 </tool>
206
207 """
208 newXML="""<tool id="%(toolid)s" name="%(toolname)s" version="%(tool_version)s">
209 %(tooldesc)s
210 %(command)s
211 <inputs>
212 %(inputs)s
213 </inputs>
214 <outputs>
215 %(outputs)s
216 </outputs>
217 <configfiles>
218 <configfile name="runMe">
219 %(script)s
220 </configfile>
221 </configfiles>
222 %(tooltests)s
223 <help>
224 %(help)s
225 </help>
226 </tool>""" # needs a dict with toolname, toolid, interpreter, scriptname, command, inputs as a multi line string ready to write, outputs ditto, help ditto
227
228 newCommand="""<command interpreter="python">
229 %(toolname)s.py --script_path "$runMe" --interpreter "%(interpreter)s"
230 --tool_name "%(toolname)s" %(command_inputs)s %(command_outputs)s
231 </command>""" # may NOT be an input or htmlout
232 tooltestsTabOnly = """<tests><test>
233 <param name="input1" value="%(test1Input)s" ftype="tabular"/>
234 <param name="job_name" value="test1"/>
235 <param name="runMe" value="$runMe"/>
236 <output name="tab_file" file="%(test1Output)s" ftype="tabular"/>
237 </test></tests>"""
238 tooltestsHTMLOnly = """<tests><test>
239 <param name="input1" value="%(test1Input)s" ftype="tabular"/>
240 <param name="job_name" value="test1"/>
241 <param name="runMe" value="$runMe"/>
242 <output name="html_file" file="%(test1HTML)s" ftype="html" lines_diff="5"/>
243 </test></tests>"""
244 tooltestsBoth = """<tests><test>
245 <param name="input1" value="%(test1Input)s" ftype="tabular"/>
246 <param name="job_name" value="test1"/>
247 <param name="runMe" value="$runMe"/>
248 <output name="tab_file" file="%(test1Output)s" ftype="tabular" />
249 <output name="html_file" file="%(test1HTML)s" ftype="html" lines_diff="10"/>
250 </test></tests>"""
251 xdict = {}
252 xdict['tool_version'] = self.opts.tool_version
253 xdict['test1Input'] = self.test1Input
254 xdict['test1HTML'] = self.test1HTML
255 xdict['test1Output'] = self.test1Output
256 if self.opts.make_HTML and self.opts.output_tab <> 'None':
257 xdict['tooltests'] = tooltestsBoth % xdict
258 elif self.opts.make_HTML:
259 xdict['tooltests'] = tooltestsHTMLOnly % xdict
260 else:
261 xdict['tooltests'] = tooltestsTabOnly % xdict
262 xdict['script'] = self.escapedScript
263 # configfile is least painful way to embed script to avoid external dependencies
264 # but requires escaping of <, > and $ to avoid Mako parsing
265 if self.opts.help_text:
266 xdict['help'] = open(self.opts.help_text,'r').read()
267 else:
268 xdict['help'] = 'Please ask the tool author for help as none was supplied at tool generation'
269 coda = ['**Script**','Pressing execute will run the following code over your input file and generate some outputs in your history::']
270 coda.append(self.indentedScript)
271 coda.append('**Attribution** This Galaxy tool was created by %s at %s\nusing the Galaxy Tool Factory.' % (self.opts.user_email,timenow()))
272 coda.append('See %s for details of that project' % (toolFactoryURL))
273 coda.append('Please cite: Creating re-usable tools from scripts: The Galaxy Tool Factory. Ross Lazarus; Antony Kaspi; Mark Ziemann; The Galaxy Team. ')
274 coda.append('Bioinformatics 2012; doi: 10.1093/bioinformatics/bts573')
275 xdict['help'] = '%s\n%s' % (xdict['help'],'\n'.join(coda))
276 if self.opts.tool_desc:
277 xdict['tooldesc'] = '<description>%s</description>' % self.opts.tool_desc
278 else:
279 xdict['tooldesc'] = ''
280 xdict['command_outputs'] = ''
281 xdict['outputs'] = ''
282 if self.opts.input_tab <> 'None':
283 xdict['command_inputs'] = '--input_tab "$input1" ' # the space may matter a lot if we append something
284 xdict['inputs'] = '<param name="input1" type="data" format="%s" label="Select a suitable input file from your history"/> \n' % self.inputFormats
285 else:
286 xdict['command_inputs'] = '' # assume no input - eg a random data generator
287 xdict['inputs'] = ''
288 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
289 xdict['toolname'] = self.toolname
290 xdict['toolid'] = self.toolid
291 xdict['interpreter'] = self.opts.interpreter
292 xdict['scriptname'] = self.sfile
293 if self.opts.make_HTML:
294 xdict['command_outputs'] += ' --output_dir "$html_file.files_path" --output_html "$html_file" --make_HTML "yes" '
295 xdict['outputs'] += ' <data format="html" name="html_file" label="${job_name}.html"/>\n'
296 if self.opts.output_tab <> 'None':
297 xdict['command_outputs'] += ' --output_tab "$tab_file"'
298 xdict['outputs'] += ' <data format="%s" name="tab_file" label="${job_name}"/>\n' % self.outFormats
299 xdict['command'] = newCommand % xdict
300 xmls = newXML % xdict
301 xf = open(self.xmlfile,'w')
302 xf.write(xmls)
303 xf.write('\n')
304 xf.close()
305 # ready for the tarball
306
307
308 def makeTooltar(self):
309 """
310 a tool is a gz tarball with eg
311 /toolname/tool.xml /toolname/tool.py /toolname/test-data/test1_in.foo ...
312 """
313 retval = self.run()
314 if retval:
315 print >> sys.stderr,'## Run failed. Cannot build yet. Please fix and retry'
316 sys.exit(1)
317 self.makeXML()
318 tdir = self.toolname
319 os.mkdir(tdir)
320 if self.opts.input_tab <> 'None': # no reproducible test otherwise? TODO: maybe..
321 testdir = os.path.join(tdir,'test-data')
322 os.mkdir(testdir) # make tests directory
323 shutil.copyfile(self.opts.input_tab,os.path.join(testdir,self.test1Input))
324 if self.opts.output_tab <> 'None':
325 shutil.copyfile(self.opts.output_tab,os.path.join(testdir,self.test1Output))
326 if self.opts.make_HTML:
327 shutil.copyfile(self.opts.output_html,os.path.join(testdir,self.test1HTML))
328 if self.opts.output_dir:
329 shutil.copyfile(self.tlog,os.path.join(testdir,'test1_out.log'))
330 op = '%s.py' % self.toolname # new name
331 outpiname = os.path.join(tdir,op) # path for the tool tarball
332 pyin = os.path.basename(self.pyfile) # our name - we rewrite ourselves (TM)
333 notes = ['# %s - a self annotated version of %s generated by running %s\n' % (op,pyin,pyin),]
334 notes.append('# to make a new Galaxy tool called %s\n' % self.toolname)
335 notes.append('# User %s at %s\n' % (self.opts.user_email,timenow()))
336 pi = open(self.pyfile,'r').readlines() # our code becomes new tool wrapper (!) - first Galaxy worm
337 notes += pi
338 outpi = open(outpiname,'w')
339 outpi.write(''.join(notes))
340 outpi.write('\n')
341 outpi.close()
342 stname = os.path.join(tdir,self.sfile)
343 if not os.path.exists(stname):
344 shutil.copyfile(self.sfile, stname)
345 xtname = os.path.join(tdir,self.xmlfile)
346 if not os.path.exists(xtname):
347 shutil.copyfile(self.xmlfile,xtname)
348 tarpath = "%s.gz" % self.toolname
349 tar = tarfile.open(tarpath, "w:gz")
350 tar.add(tdir,arcname=self.toolname)
351 tar.close()
352 shutil.copyfile(tarpath,self.opts.new_tool)
353 shutil.rmtree(tdir)
354 ## TODO: replace with optional direct upload to local toolshed?
355 return retval
356
357
358 def compressPDF(self,inpdf=None,thumbformat='png'):
359 """need absolute path to pdf
360 note that GS gets confoozled if no $TMP or $TEMP
361 so we set it
362 """
363 assert os.path.isfile(inpdf), "## Input %s supplied to %s compressPDF not found" % (inpdf,self.myName)
364 our_env = os.environ.copy()
365 if not (our_env.get('TMP',None) or our_env.get('TEMP',None)):
366 our_env['TMP'] = '/tmp'
367 if not self.temp_warned:
368 print >> sys.stdout,'## WARNING - no $TMP or $TEMP!!! Please fix - using /tmp temporarily'
369 self.temp_warned = True
370 hlog = os.path.join(self.opts.output_dir,"compress_%s.txt" % os.path.basename(inpdf))
371 sto = open(hlog,'w')
372 outpdf = '%s_compressed' % inpdf
373 cl = ["gs", "-sDEVICE=pdfwrite", "-dNOPAUSE", "-dUseCIEColor", "-dBATCH","-dPDFSETTINGS=/printer", "-sOutputFile=%s" % outpdf,inpdf]
374 x = subprocess.Popen(cl,stdout=sto,stderr=sto,cwd=self.opts.output_dir,env=our_env)
375 retval1 = x.wait()
376 sto.close()
377 if retval1 == 0:
378 os.unlink(inpdf)
379 shutil.move(outpdf,inpdf)
380 os.unlink(hlog)
381 else:
382 x = open(hlog,'r').readlines()
383 print >> sys.stdout,x
384 hlog = os.path.join(self.opts.output_dir,"thumbnail_%s.txt" % os.path.basename(inpdf))
385 sto = open(hlog,'w')
386 outpng = '%s.%s' % (os.path.splitext(inpdf)[0],thumbformat)
387 if self.useGM:
388 cl2 = ['gm', 'convert', inpdf, outpng]
389 else: # assume imagemagick
390 cl2 = ['convert', inpdf, outpng]
391 x = subprocess.Popen(cl2,stdout=sto,stderr=sto,cwd=self.opts.output_dir,env=our_env)
392 retval2 = x.wait()
393 sto.close()
394 if retval2 <> 0:
395 x = open(hlog,'r').readlines()
396 print >> sys.stdout,x
397 else:
398 os.unlink(hlog)
399 retval = retval1 or retval2
400 return retval
401
402
403 def getfSize(self,fpath,outpath):
404 """
405 format a nice file size string
406 """
407 size = ''
408 fp = os.path.join(outpath,fpath)
409 if os.path.isfile(fp):
410 size = '0 B'
411 n = float(os.path.getsize(fp))
412 if n > 2**20:
413 size = '%1.1f MB' % (n/2**20)
414 elif n > 2**10:
415 size = '%1.1f KB' % (n/2**10)
416 elif n > 0:
417 size = '%d B' % (int(n))
418 return size
419
420 def makeHtml(self):
421 """ Create an HTML file content to list all the artifacts found in the output_dir
422 """
423
424 galhtmlprefix = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
425 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
426 <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
427 <meta name="generator" content="Galaxy %s tool output - see http://g2.trac.bx.psu.edu/" />
428 <title></title>
429 <link rel="stylesheet" href="/static/style/base.css" type="text/css" />
430 </head>
431 <body>
432 <div class="toolFormBody">
433 """
434 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/>"""
435 galhtmlpostfix = """</div></body></html>\n"""
436
437 flist = os.listdir(self.opts.output_dir)
438 flist = [x for x in flist if x <> 'Rplots.pdf']
439 flist.sort()
440 html = []
441 html.append(galhtmlprefix % progname)
442 html.append('<div class="infomessage">Galaxy Tool "%s" run at %s</div><br/>' % (self.toolname,timenow()))
443 fhtml = []
444 if len(flist) > 0:
445 logfiles = [x for x in flist if x.lower().endswith('.log')] # log file names determine sections
446 logfiles.sort()
447 logfiles = [x for x in logfiles if os.path.abspath(x) <> os.path.abspath(self.tlog)]
448 logfiles.append(os.path.abspath(self.tlog)) # make it the last one
449 pdflist = []
450 npdf = len([x for x in flist if os.path.splitext(x)[-1].lower() == '.pdf'])
451 for rownum,fname in enumerate(flist):
452 dname,e = os.path.splitext(fname)
453 sfsize = self.getfSize(fname,self.opts.output_dir)
454 if e.lower() == '.pdf' : # compress and make a thumbnail
455 thumb = '%s.%s' % (dname,self.thumbformat)
456 pdff = os.path.join(self.opts.output_dir,fname)
457 retval = self.compressPDF(inpdf=pdff,thumbformat=self.thumbformat)
458 if retval == 0:
459 pdflist.append((fname,thumb))
460 else:
461 pdflist.append((fname,fname))
462 if (rownum+1) % 2 == 0:
463 fhtml.append('<tr class="odd_row"><td><a href="%s">%s</a></td><td>%s</td></tr>' % (fname,fname,sfsize))
464 else:
465 fhtml.append('<tr><td><a href="%s">%s</a></td><td>%s</td></tr>' % (fname,fname,sfsize))
466 for logfname in logfiles: # expect at least tlog - if more
467 if os.path.abspath(logfname) == os.path.abspath(self.tlog): # handled later
468 sectionname = 'All tool run'
469 if (len(logfiles) > 1):
470 sectionname = 'Other'
471 ourpdfs = pdflist
472 else:
473 realname = os.path.basename(logfname)
474 sectionname = os.path.splitext(realname)[0].split('_')[0] # break in case _ added to log
475 ourpdfs = [x for x in pdflist if os.path.basename(x[0]).split('_')[0] == sectionname]
476 pdflist = [x for x in pdflist if os.path.basename(x[0]).split('_')[0] <> sectionname] # remove
477 nacross = 1
478 npdf = len(ourpdfs)
479
480 if npdf > 0:
481 nacross = math.sqrt(npdf) ## int(round(math.log(npdf,2)))
482 if int(nacross)**2 != npdf:
483 nacross += 1
484 nacross = int(nacross)
485 width = min(400,int(1200/nacross))
486 html.append('<div class="toolFormTitle">%s images and outputs</div>' % sectionname)
487 html.append('(Click on a thumbnail image to download the corresponding original PDF image)<br/>')
488 ntogo = nacross # counter for table row padding with empty cells
489 html.append('<div><table class="simple" cellpadding="2" cellspacing="2">\n<tr>')
490 for i,paths in enumerate(ourpdfs):
491 fname,thumb = paths
492 s= """<td><a href="%s"><img src="%s" title="Click to download a PDF of %s" hspace="5" width="%d"
493 alt="Image called %s"/></a></td>\n""" % (fname,thumb,fname,width,fname)
494 if ((i+1) % nacross == 0):
495 s += '</tr>\n'
496 ntogo = 0
497 if i < (npdf - 1): # more to come
498 s += '<tr>'
499 ntogo = nacross
500 else:
501 ntogo -= 1
502 html.append(s)
503 if html[-1].strip().endswith('</tr>'):
504 html.append('</table></div>\n')
505 else:
506 if ntogo > 0: # pad
507 html.append('<td>&nbsp;</td>'*ntogo)
508 html.append('</tr></table></div>\n')
509 logt = open(logfname,'r').readlines()
510 logtext = [x for x in logt if x.strip() > '']
511 html.append('<div class="toolFormTitle">%s log output</div>' % sectionname)
512 if len(logtext) > 1:
513 html.append('\n<pre>\n')
514 html += logtext
515 html.append('\n</pre>\n')
516 else:
517 html.append('%s is empty<br/>' % logfname)
518 if len(fhtml) > 0:
519 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')
520 fhtml.append('</table></div><br/>')
521 html.append('<div class="toolFormTitle">All output files available for downloading</div>\n')
522 html += fhtml # add all non-pdf files to the end of the display
523 else:
524 html.append('<div class="warningmessagelarge">### Error - %s returned no files - please confirm that parameters are sane</div>' % self.opts.interpreter)
525 html.append(galhtmlpostfix)
526 htmlf = file(self.opts.output_html,'w')
527 htmlf.write('\n'.join(html))
528 htmlf.write('\n')
529 htmlf.close()
530 self.html = html
531
532 def run(self):
533 """
534 scripts must be small enough not to fill the pipe!
535 """
536 my_env = os.environ.copy()
537 if self.opts.output_dir:
538 ste = open(self.elog,'w')
539 sto = open(self.tlog,'w')
540 sto.write('## Toolfactory running %s as %s script\n' % (self.toolname,self.opts.interpreter))
541 sto.flush()
542 p = subprocess.Popen(self.cl,shell=False,stdout=sto,stderr=ste,cwd=self.opts.output_dir,env=my_env)
543 retval = p.wait()
544 sto.close()
545 ste.close()
546 # get stderr, allowing for case where it's very large
547 tmp_stderr = open( self.elog, 'rb' )
548 stderr = ''
549 try:
550 while True:
551 stderr += tmp_stderr.read( buffsize )
552 if not stderr or len( stderr ) % buffsize != 0:
553 break
554 except OverflowError:
555 pass
556 tmp_stderr.close()
557 else:
558 p = subprocess.Popen(self.cl,shell=False,env=my_env)
559 retval = p.wait()
560 if self.opts.make_HTML:
561 self.makeHtml()
562 return retval
563
564
565 def remove_me_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 ste = open(self.elog,'w')
572 sto = open(self.tlog,'w')
573 sto.write(s)
574 sto.flush()
575 p = subprocess.Popen(self.cl,shell=False,stdout=sto,stderr=ste,cwd=self.opts.output_dir)
576 else:
577 p = subprocess.Popen(self.cl,shell=False)
578 retval = p.wait()
579 if self.opts.output_dir:
580 sto.close()
581 ste.close()
582 # get stderr, allowing for case where it's very large
583 tmp_stderr = open(self.elog, 'rb' )
584 stderr = ''
585 try:
586 while True:
587 stderr += tmp_stderr.read( buffsize )
588 if not stderr or len( stderr ) % buffsize != 0:
589 break
590 except OverflowError:
591 pass
592 tmp_stderr.close()
593 return retval
594
595
596 def main():
597 u = """
598 This is a Galaxy wrapper. It expects to be called by a special purpose tool.xml as (eg):
599 <command interpreter="python">rgToolFactory.py --script_path "$scriptPath" --tool_name "foo" --interpreter "Rscript"
600 </command>
601 The tool writes a script to a scriptPath using a configfile.
602 Some things in the script are templates.
603 The idea here is that depending on how this code is called, it uses the specified interpreter
604 to run a (hopefully correct) script/template. Optionally generates a clone of itself
605 which will run that same script/template as a toolshed repository tarball for uploading to a toolshed.
606 There's now an updated version which allows arbitrary parameters.
607 And so it goes.
608 """
609 op = optparse.OptionParser()
610 a = op.add_option
611 a('--script_path',default=None)
612 a('--tool_name',default=None)
613 a('--interpreter',default=None)
614 a('--output_dir',default=None)
615 a('--output_html',default=None)
616 a('--input_tab',default="None")
617 a('--output_tab',default="None")
618 a('--user_email',default='Unknown')
619 a('--bad_user',default=None)
620 a('--make_Tool',default=None)
621 a('--make_HTML',default=None)
622 a('--help_text',default=None)
623 a('--tool_desc',default=None)
624 a('--new_tool',default=None)
625 a('--tool_version',default=None)
626 opts, args = op.parse_args()
627 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)
628 assert opts.tool_name,'## Tool Factory expects a tool name - eg --tool_name=DESeq'
629 assert opts.interpreter,'## Tool Factory wrapper expects an interpreter - eg --interpreter=Rscript'
630 assert os.path.isfile(opts.script_path),'## Tool Factory wrapper expects a script path - eg --script_path=foo.R'
631 if opts.output_dir:
632 try:
633 os.makedirs(opts.output_dir)
634 except:
635 pass
636 r = ScriptRunner(opts)
637 if opts.make_Tool:
638 retcode = r.makeTooltar()
639 else:
640 retcode = r.run()
641 os.unlink(r.sfile)
642 if retcode:
643 sys.exit(retcode) # indicate failure to job runner
644
645
646 if __name__ == "__main__":
647 main()
648
649