comparison rgToolFactory.py~ @ 45:6d9d2c9679ec draft

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