comparison rgToolFactoryMultIn.py @ 34:c6fdf2c6d0f4 draft

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