comparison rgToolFactory.py @ 7:1fcf3fda325f draft

major fixes with GS and graphicsmagick dependencies included
author fubar
date Wed, 19 Mar 2014 21:06:28 -0400
parents
children 704ccaade924
comparison
equal deleted inserted replaced
6:cb4937b0c21d 7:1fcf3fda325f
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 # march 2014
11 # added ghostscript and graphicsmagick as dependencies
12 # fixed a wierd problem where gs was trying to use the new_files_path from universe (database/tmp) as ./database/tmp
13 # errors ensued
14 #
15 # august 2013
16 # found a problem with GS if $TMP or $TEMP missing - now inject /tmp and warn
17 #
18 # july 2013
19 # added ability to combine images and individual log files into html output
20 # just make sure there's a log file foo.log and it will be output
21 # together with all images named like "foo_*.pdf
22 # otherwise old format for html
23 #
24 # January 2013
25 # problem pointed out by Carlos Borroto
26 # added escaping for <>$ - thought I did that ages ago...
27 #
28 # August 11 2012
29 # changed to use shell=False and cl as a sequence
30
31 # This is a Galaxy tool factory for simple scripts in python, R or whatever ails ye.
32 # It also serves as the wrapper for the new tool.
33 #
34 # you paste and run your script
35 # Only works for simple scripts that read one input from the history.
36 # Optionally can write one new history dataset,
37 # and optionally collect any number of outputs into links on an autogenerated HTML page.
38
39 # DO NOT install on a public or important site - please.
40
41 # installed generated tools are fine if the script is safe.
42 # They just run normally and their user cannot do anything unusually insecure
43 # but please, practice safe toolshed.
44 # Read the fucking code before you install any tool
45 # especially this one
46
47 # After you get the script working on some test data, you can
48 # optionally generate a toolshed compatible gzip file
49 # containing your script safely wrapped as an ordinary Galaxy script in your local toolshed for
50 # safe and largely automated installation in a production Galaxy.
51
52 # If you opt for an HTML output, you get all the script outputs arranged
53 # as a single Html history item - all output files are linked, thumbnails for all the pdfs.
54 # Ugly but really inexpensive.
55 #
56 # Patches appreciated please.
57 #
58 #
59 # long route to June 2012 product
60 # Behold the awesome power of Galaxy and the toolshed with the tool factory to bind them
61 # derived from an integrated script model
62 # called rgBaseScriptWrapper.py
63 # Note to the unwary:
64 # This tool allows arbitrary scripting on your Galaxy as the Galaxy user
65 # There is nothing stopping a malicious user doing whatever they choose
66 # Extremely dangerous!!
67 # Totally insecure. So, trusted users only
68 #
69 # preferred model is a developer using their throw away workstation instance - ie a private site.
70 # no real risk. The universe_wsgi.ini admin_users string is checked - only admin users are permitted to run this tool.
71 #
72
73 import sys
74 import shutil
75 import subprocess
76 import os
77 import time
78 import tempfile
79 import optparse
80 import tarfile
81 import re
82 import shutil
83 import math
84
85 progname = os.path.split(sys.argv[0])[1]
86 myversion = 'V001.1 March 2014'
87 verbose = False
88 debug = False
89 toolFactoryURL = 'https://bitbucket.org/fubar/galaxytoolfactory'
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 if opts.output_dir: # may not want these complexities
146 self.tlog = os.path.join(self.opts.output_dir,"%s_runner.log" % self.toolname)
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 hlog = os.path.join(self.opts.output_dir,"compress_%s.txt" % os.path.basename(inpdf))
374 sto = open(hlog,'a')
375 our_env = os.environ.copy()
376 our_tmp = our_env.get('TMP',None)
377 if not our_tmp:
378 our_tmp = our_env.get('TEMP',None)
379 if not (our_tmp and os.path.exists(our_tmp)):
380 newtmp = os.path.join(self.opts.output_dir,'tmp')
381 try:
382 os.mkdir(newtmp)
383 except:
384 sto.write('## WARNING - cannot make %s - it may exist or permissions need fixing\n' % newtmp)
385 our_env['TEMP'] = newtmp
386 if not self.temp_warned:
387 sto.write('## WARNING - no $TMP or $TEMP!!! Please fix - using %s temporarily\n' % newtmp)
388 self.temp_warned = True
389 outpdf = '%s_compressed' % inpdf
390 cl = ["gs", "-sDEVICE=pdfwrite", "-dNOPAUSE", "-dUseCIEColor", "-dBATCH","-dPDFSETTINGS=/printer", "-sOutputFile=%s" % outpdf,inpdf]
391 x = subprocess.Popen(cl,stdout=sto,stderr=sto,cwd=self.opts.output_dir,env=our_env)
392 retval1 = x.wait()
393 sto.close()
394 if retval1 == 0:
395 os.unlink(inpdf)
396 shutil.move(outpdf,inpdf)
397 os.unlink(hlog)
398 hlog = os.path.join(self.opts.output_dir,"thumbnail_%s.txt" % os.path.basename(inpdf))
399 sto = open(hlog,'w')
400 outpng = '%s.%s' % (os.path.splitext(inpdf)[0],thumbformat)
401 if self.useGM:
402 cl2 = ['gm', 'convert', inpdf, outpng]
403 else: # assume imagemagick
404 cl2 = ['convert', inpdf, outpng]
405 x = subprocess.Popen(cl2,stdout=sto,stderr=sto,cwd=self.opts.output_dir,env=our_env)
406 retval2 = x.wait()
407 sto.close()
408 if retval2 == 0:
409 os.unlink(hlog)
410 retval = retval1 or retval2
411 return retval
412
413
414 def getfSize(self,fpath,outpath):
415 """
416 format a nice file size string
417 """
418 size = ''
419 fp = os.path.join(outpath,fpath)
420 if os.path.isfile(fp):
421 size = '0 B'
422 n = float(os.path.getsize(fp))
423 if n > 2**20:
424 size = '%1.1f MB' % (n/2**20)
425 elif n > 2**10:
426 size = '%1.1f KB' % (n/2**10)
427 elif n > 0:
428 size = '%d B' % (int(n))
429 return size
430
431 def makeHtml(self):
432 """ Create an HTML file content to list all the artifacts found in the output_dir
433 """
434
435 galhtmlprefix = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
436 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
437 <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
438 <meta name="generator" content="Galaxy %s tool output - see http://g2.trac.bx.psu.edu/" />
439 <title></title>
440 <link rel="stylesheet" href="/static/style/base.css" type="text/css" />
441 </head>
442 <body>
443 <div class="toolFormBody">
444 """
445 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/>"""
446 galhtmlpostfix = """</div></body></html>\n"""
447
448 flist = os.listdir(self.opts.output_dir)
449 flist = [x for x in flist if x <> 'Rplots.pdf']
450 flist.sort()
451 html = []
452 html.append(galhtmlprefix % progname)
453 html.append('<div class="infomessage">Galaxy Tool "%s" run at %s</div><br/>' % (self.toolname,timenow()))
454 fhtml = []
455 if len(flist) > 0:
456 logfiles = [x for x in flist if x.lower().endswith('.log')] # log file names determine sections
457 logfiles.sort()
458 logfiles = [x for x in logfiles if os.path.abspath(x) <> os.path.abspath(self.tlog)]
459 logfiles.append(os.path.abspath(self.tlog)) # make it the last one
460 pdflist = []
461 npdf = len([x for x in flist if os.path.splitext(x)[-1].lower() == '.pdf'])
462 for rownum,fname in enumerate(flist):
463 dname,e = os.path.splitext(fname)
464 sfsize = self.getfSize(fname,self.opts.output_dir)
465 if e.lower() == '.pdf' : # compress and make a thumbnail
466 thumb = '%s.%s' % (dname,self.thumbformat)
467 pdff = os.path.join(self.opts.output_dir,fname)
468 retval = self.compressPDF(inpdf=pdff,thumbformat=self.thumbformat)
469 if retval == 0:
470 pdflist.append((fname,thumb))
471 else:
472 pdflist.append((fname,fname))
473 if (rownum+1) % 2 == 0:
474 fhtml.append('<tr class="odd_row"><td><a href="%s">%s</a></td><td>%s</td></tr>' % (fname,fname,sfsize))
475 else:
476 fhtml.append('<tr><td><a href="%s">%s</a></td><td>%s</td></tr>' % (fname,fname,sfsize))
477 for logfname in logfiles: # expect at least tlog - if more
478 if os.path.abspath(logfname) == os.path.abspath(self.tlog): # handled later
479 sectionname = 'All tool run'
480 if (len(logfiles) > 1):
481 sectionname = 'Other'
482 ourpdfs = pdflist
483 else:
484 realname = os.path.basename(logfname)
485 sectionname = os.path.splitext(realname)[0].split('_')[0] # break in case _ added to log
486 ourpdfs = [x for x in pdflist if os.path.basename(x[0]).split('_')[0] == sectionname]
487 pdflist = [x for x in pdflist if os.path.basename(x[0]).split('_')[0] <> sectionname] # remove
488 nacross = 1
489 npdf = len(ourpdfs)
490
491 if npdf > 0:
492 nacross = math.sqrt(npdf) ## int(round(math.log(npdf,2)))
493 if int(nacross)**2 != npdf:
494 nacross += 1
495 nacross = int(nacross)
496 width = min(400,int(1200/nacross))
497 html.append('<div class="toolFormTitle">%s images and outputs</div>' % sectionname)
498 html.append('(Click on a thumbnail image to download the corresponding original PDF image)<br/>')
499 ntogo = nacross # counter for table row padding with empty cells
500 html.append('<div><table class="simple" cellpadding="2" cellspacing="2">\n<tr>')
501 for i,paths in enumerate(ourpdfs):
502 fname,thumb = paths
503 s= """<td><a href="%s"><img src="%s" title="Click to download a PDF of %s" hspace="5" width="%d"
504 alt="Image called %s"/></a></td>\n""" % (fname,thumb,fname,width,fname)
505 if ((i+1) % nacross == 0):
506 s += '</tr>\n'
507 ntogo = 0
508 if i < (npdf - 1): # more to come
509 s += '<tr>'
510 ntogo = nacross
511 else:
512 ntogo -= 1
513 html.append(s)
514 if html[-1].strip().endswith('</tr>'):
515 html.append('</table></div>\n')
516 else:
517 if ntogo > 0: # pad
518 html.append('<td>&nbsp;</td>'*ntogo)
519 html.append('</tr></table></div>\n')
520 logt = open(logfname,'r').readlines()
521 logtext = [x for x in logt if x.strip() > '']
522 html.append('<div class="toolFormTitle">%s log output</div>' % sectionname)
523 if len(logtext) > 1:
524 html.append('\n<pre>\n')
525 html += logtext
526 html.append('\n</pre>\n')
527 else:
528 html.append('%s is empty<br/>' % logfname)
529 if len(fhtml) > 0:
530 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')
531 fhtml.append('</table></div><br/>')
532 html.append('<div class="toolFormTitle">All output files available for downloading</div>\n')
533 html += fhtml # add all non-pdf files to the end of the display
534 else:
535 html.append('<div class="warningmessagelarge">### Error - %s returned no files - please confirm that parameters are sane</div>' % self.opts.interpreter)
536 html.append(galhtmlpostfix)
537 htmlf = file(self.opts.output_html,'w')
538 htmlf.write('\n'.join(html))
539 htmlf.write('\n')
540 htmlf.close()
541 self.html = html
542
543
544 def run(self):
545 """
546 scripts must be small enough not to fill the pipe!
547 """
548 if self.treatbashSpecial and self.opts.interpreter in ['bash','sh']:
549 retval = self.runBash()
550 else:
551 if self.opts.output_dir:
552 ste = open(self.elog,'w')
553 sto = open(self.tlog,'w')
554 sto.write('## Toolfactory generated command line = %s\n' % ' '.join(self.cl))
555 sto.flush()
556 p = subprocess.Popen(self.cl,shell=False,stdout=sto,stderr=ste,stdin=subprocess.PIPE,cwd=self.opts.output_dir)
557 else:
558 p = subprocess.Popen(self.cl,shell=False,stdin=subprocess.PIPE)
559 p.stdin.write(self.script)
560 p.stdin.close()
561 retval = p.wait()
562 if self.opts.output_dir:
563 sto.close()
564 ste.close()
565 err = open(self.elog,'r').readlines()
566 if retval <> 0 and err: # problem
567 print >> sys.stderr,err
568 if self.opts.make_HTML:
569 self.makeHtml()
570 return retval
571
572 def runBash(self):
573 """
574 cannot use - for bash so use self.sfile
575 """
576 if self.opts.output_dir:
577 s = '## Toolfactory generated command line = %s\n' % ' '.join(self.cl)
578 sto = open(self.tlog,'w')
579 sto.write(s)
580 sto.flush()
581 p = subprocess.Popen(self.cl,shell=False,stdout=sto,stderr=sto,cwd=self.opts.output_dir)
582 else:
583 p = subprocess.Popen(self.cl,shell=False)
584 retval = p.wait()
585 if self.opts.output_dir:
586 sto.close()
587 if self.opts.make_HTML:
588 self.makeHtml()
589 return retval
590
591
592 def main():
593 u = """
594 This is a Galaxy wrapper. It expects to be called by a special purpose tool.xml as:
595 <command interpreter="python">rgBaseScriptWrapper.py --script_path "$scriptPath" --tool_name "foo" --interpreter "Rscript"
596 </command>
597 """
598 op = optparse.OptionParser()
599 a = op.add_option
600 a('--script_path',default=None)
601 a('--tool_name',default=None)
602 a('--interpreter',default=None)
603 a('--output_dir',default=None)
604 a('--output_html',default=None)
605 a('--input_tab',default="None")
606 a('--output_tab',default="None")
607 a('--user_email',default='Unknown')
608 a('--bad_user',default=None)
609 a('--make_Tool',default=None)
610 a('--make_HTML',default=None)
611 a('--help_text',default=None)
612 a('--tool_desc',default=None)
613 a('--new_tool',default=None)
614 a('--tool_version',default=None)
615 opts, args = op.parse_args()
616 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)
617 assert opts.tool_name,'## Tool Factory expects a tool name - eg --tool_name=DESeq'
618 assert opts.interpreter,'## Tool Factory wrapper expects an interpreter - eg --interpreter=Rscript'
619 assert os.path.isfile(opts.script_path),'## Tool Factory wrapper expects a script path - eg --script_path=foo.R'
620 if opts.output_dir:
621 try:
622 os.makedirs(opts.output_dir)
623 except:
624 pass
625 r = ScriptRunner(opts)
626 if opts.make_Tool:
627 retcode = r.makeTooltar()
628 else:
629 retcode = r.run()
630 os.unlink(r.sfile)
631 if retcode:
632 sys.exit(retcode) # indicate failure to job runner
633
634
635 if __name__ == "__main__":
636 main()
637
638