comparison rgToolFactory.py @ 39:64f7bd086146 draft

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