comparison rgToolFactory.py @ 22:4e3aa95ed3ac draft

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