comparison rgToolFactory.py @ 26:63082502722a draft

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