comparison rgToolFactory.py @ 29:bff4c9bfabc7 draft

Fixes for escaping all xml characters in help and code - thanks to Marius van den Beek for pointing these out
author fubar
date Thu, 07 Aug 2014 22:11:02 -0400
parents
children fb3fa6a2874d
comparison
equal deleted inserted replaced
28:03bb25f38ea8 29:bff4c9bfabc7
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' % html_escape(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 helptext = open(self.opts.help_text,'r').readlines()
322 helptext = [html_escape(x) for x in helptext] # must html escape here too - thanks to Marius van den Beek
323 xdict['help'] = ''.join([x for x in helptext])
324 else:
325 xdict['help'] = 'Please ask the tool author (%s) for help as none was supplied at tool generation\n' % (self.opts.user_email)
326 coda = ['**Script**','Pressing execute will run the following code over your input file and generate some outputs in your history::']
327 coda.append('\n')
328 coda.append(self.indentedScript)
329 coda.append('\n**Attribution**\nThis Galaxy tool was created by %s at %s\nusing the Galaxy Tool Factory.\n' % (self.opts.user_email,timenow()))
330 coda.append('See %s for details of that project' % (toolFactoryURL))
331 coda.append('Please cite: Creating re-usable tools from scripts: The Galaxy Tool Factory. Ross Lazarus; Antony Kaspi; Mark Ziemann; The Galaxy Team. ')
332 coda.append('Bioinformatics 2012; doi: 10.1093/bioinformatics/bts573\n')
333 xdict['help'] = '%s\n%s' % (xdict['help'],'\n'.join(coda))
334 if self.opts.tool_desc:
335 xdict['tooldesc'] = '<description>%s</description>' % self.opts.tool_desc
336 else:
337 xdict['tooldesc'] = ''
338 xdict['command_outputs'] = ''
339 xdict['outputs'] = ''
340 if self.opts.input_tab <> 'None':
341 xdict['command_inputs'] = '--input_tab "$input1" ' # the space may matter a lot if we append something
342 xdict['inputs'] = '<param name="input1" type="data" format="%s" label="Select a suitable input file from your history"/> \n' % self.inputFormats
343 else:
344 xdict['command_inputs'] = '' # assume no input - eg a random data generator
345 xdict['inputs'] = ''
346 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
347 xdict['toolname'] = self.toolname
348 xdict['toolid'] = self.toolid
349 xdict['interpreter'] = self.opts.interpreter
350 xdict['scriptname'] = self.sfile
351 if self.opts.make_HTML:
352 xdict['command_outputs'] += ' --output_dir "$html_file.files_path" --output_html "$html_file" --make_HTML "yes"'
353 xdict['outputs'] += ' <data format="html" name="html_file" label="${job_name}.html"/>\n'
354 else:
355 xdict['command_outputs'] += ' --output_dir "./"'
356 if self.opts.output_tab <> 'None':
357 xdict['command_outputs'] += ' --output_tab "$tab_file"'
358 xdict['outputs'] += ' <data format="%s" name="tab_file" label="${job_name}"/>\n' % self.outFormats
359 xdict['command'] = newCommand % xdict
360 xmls = newXML % xdict
361 xf = open(self.xmlfile,'w')
362 xf.write(xmls)
363 xf.write('\n')
364 xf.close()
365 # ready for the tarball
366
367
368 def makeTooltar(self):
369 """
370 a tool is a gz tarball with eg
371 /toolname/tool.xml /toolname/tool.py /toolname/test-data/test1_in.foo ...
372 """
373 retval = self.run()
374 if retval:
375 print >> sys.stderr,'## Run failed. Cannot build yet. Please fix and retry'
376 sys.exit(1)
377 tdir = self.toolname
378 os.mkdir(tdir)
379 self.makeXML()
380 if self.opts.make_HTML:
381 if self.opts.help_text:
382 hlp = open(self.opts.help_text,'r').read()
383 else:
384 hlp = 'Please ask the tool author for help as none was supplied at tool generation\n'
385 if self.opts.include_dependencies:
386 tooldepcontent = toolhtmldepskel % hlp
387 depf = open(os.path.join(tdir,'tool_dependencies.xml'),'w')
388 depf.write(tooldepcontent)
389 depf.write('\n')
390 depf.close()
391 if self.opts.input_tab <> 'None': # no reproducible test otherwise? TODO: maybe..
392 testdir = os.path.join(tdir,'test-data')
393 os.mkdir(testdir) # make tests directory
394 shutil.copyfile(self.opts.input_tab,os.path.join(testdir,self.test1Input))
395 if self.opts.output_tab <> 'None':
396 shutil.copyfile(self.opts.output_tab,os.path.join(testdir,self.test1Output))
397 if self.opts.make_HTML:
398 shutil.copyfile(self.opts.output_html,os.path.join(testdir,self.test1HTML))
399 if self.opts.output_dir:
400 shutil.copyfile(self.tlog,os.path.join(testdir,'test1_out.log'))
401 outpif = '%s.py' % self.toolname # new name
402 outpiname = os.path.join(tdir,outpif) # path for the tool tarball
403 pyin = os.path.basename(self.pyfile) # our name - we rewrite ourselves (TM)
404 notes = ['# %s - a self annotated version of %s generated by running %s\n' % (outpiname,pyin,pyin),]
405 notes.append('# to make a new Galaxy tool called %s\n' % self.toolname)
406 notes.append('# User %s at %s\n' % (self.opts.user_email,timenow()))
407 pi = open(self.pyfile,'r').readlines() # our code becomes new tool wrapper (!) - first Galaxy worm
408 notes += pi
409 outpi = open(outpiname,'w')
410 outpi.write(''.join(notes))
411 outpi.write('\n')
412 outpi.close()
413 stname = os.path.join(tdir,self.sfile)
414 if not os.path.exists(stname):
415 shutil.copyfile(self.sfile, stname)
416 xtname = os.path.join(tdir,self.xmlfile)
417 if not os.path.exists(xtname):
418 shutil.copyfile(self.xmlfile,xtname)
419 tarpath = "%s.gz" % self.toolname
420 tar = tarfile.open(tarpath, "w:gz")
421 tar.add(tdir,arcname=self.toolname)
422 tar.close()
423 shutil.copyfile(tarpath,self.opts.new_tool)
424 shutil.rmtree(tdir)
425 ## TODO: replace with optional direct upload to local toolshed?
426 return retval
427
428
429 def compressPDF(self,inpdf=None,thumbformat='png'):
430 """need absolute path to pdf
431 note that GS gets confoozled if no $TMP or $TEMP
432 so we set it
433 """
434 assert os.path.isfile(inpdf), "## Input %s supplied to %s compressPDF not found" % (inpdf,self.myName)
435 hlog = os.path.join(self.opts.output_dir,"compress_%s.txt" % os.path.basename(inpdf))
436 sto = open(hlog,'a')
437 our_env = os.environ.copy()
438 our_tmp = our_env.get('TMP',None)
439 if not our_tmp:
440 our_tmp = our_env.get('TEMP',None)
441 if not (our_tmp and os.path.exists(our_tmp)):
442 newtmp = os.path.join(self.opts.output_dir,'tmp')
443 try:
444 os.mkdir(newtmp)
445 except:
446 sto.write('## WARNING - cannot make %s - it may exist or permissions need fixing\n' % newtmp)
447 our_env['TEMP'] = newtmp
448 if not self.temp_warned:
449 sto.write('## WARNING - no $TMP or $TEMP!!! Please fix - using %s temporarily\n' % newtmp)
450 self.temp_warned = True
451 outpdf = '%s_compressed' % inpdf
452 cl = ["gs", "-sDEVICE=pdfwrite", "-dNOPAUSE", "-dUseCIEColor", "-dBATCH","-dPDFSETTINGS=/printer", "-sOutputFile=%s" % outpdf,inpdf]
453 x = subprocess.Popen(cl,stdout=sto,stderr=sto,cwd=self.opts.output_dir,env=our_env)
454 retval1 = x.wait()
455 sto.close()
456 if retval1 == 0:
457 os.unlink(inpdf)
458 shutil.move(outpdf,inpdf)
459 os.unlink(hlog)
460 hlog = os.path.join(self.opts.output_dir,"thumbnail_%s.txt" % os.path.basename(inpdf))
461 sto = open(hlog,'w')
462 outpng = '%s.%s' % (os.path.splitext(inpdf)[0],thumbformat)
463 if self.useGM:
464 cl2 = ['gm', 'convert', inpdf, outpng]
465 else: # assume imagemagick
466 cl2 = ['convert', inpdf, outpng]
467 x = subprocess.Popen(cl2,stdout=sto,stderr=sto,cwd=self.opts.output_dir,env=our_env)
468 retval2 = x.wait()
469 sto.close()
470 if retval2 == 0:
471 os.unlink(hlog)
472 retval = retval1 or retval2
473 return retval
474
475
476 def getfSize(self,fpath,outpath):
477 """
478 format a nice file size string
479 """
480 size = ''
481 fp = os.path.join(outpath,fpath)
482 if os.path.isfile(fp):
483 size = '0 B'
484 n = float(os.path.getsize(fp))
485 if n > 2**20:
486 size = '%1.1f MB' % (n/2**20)
487 elif n > 2**10:
488 size = '%1.1f KB' % (n/2**10)
489 elif n > 0:
490 size = '%d B' % (int(n))
491 return size
492
493 def makeHtml(self):
494 """ Create an HTML file content to list all the artifacts found in the output_dir
495 """
496
497 galhtmlprefix = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
498 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
499 <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
500 <meta name="generator" content="Galaxy %s tool output - see http://g2.trac.bx.psu.edu/" />
501 <title></title>
502 <link rel="stylesheet" href="/static/style/base.css" type="text/css" />
503 </head>
504 <body>
505 <div class="toolFormBody">
506 """
507 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/>"""
508 galhtmlpostfix = """</div></body></html>\n"""
509
510 flist = os.listdir(self.opts.output_dir)
511 flist = [x for x in flist if x <> 'Rplots.pdf']
512 flist.sort()
513 html = []
514 html.append(galhtmlprefix % progname)
515 html.append('<div class="infomessage">Galaxy Tool "%s" run at %s</div><br/>' % (self.toolname,timenow()))
516 fhtml = []
517 if len(flist) > 0:
518 logfiles = [x for x in flist if x.lower().endswith('.log')] # log file names determine sections
519 logfiles.sort()
520 logfiles = [x for x in logfiles if os.path.abspath(x) <> os.path.abspath(self.tlog)]
521 logfiles.append(os.path.abspath(self.tlog)) # make it the last one
522 pdflist = []
523 npdf = len([x for x in flist if os.path.splitext(x)[-1].lower() == '.pdf'])
524 for rownum,fname in enumerate(flist):
525 dname,e = os.path.splitext(fname)
526 sfsize = self.getfSize(fname,self.opts.output_dir)
527 if e.lower() == '.pdf' : # compress and make a thumbnail
528 thumb = '%s.%s' % (dname,self.thumbformat)
529 pdff = os.path.join(self.opts.output_dir,fname)
530 retval = self.compressPDF(inpdf=pdff,thumbformat=self.thumbformat)
531 if retval == 0:
532 pdflist.append((fname,thumb))
533 else:
534 pdflist.append((fname,fname))
535 if (rownum+1) % 2 == 0:
536 fhtml.append('<tr class="odd_row"><td><a href="%s">%s</a></td><td>%s</td></tr>' % (fname,fname,sfsize))
537 else:
538 fhtml.append('<tr><td><a href="%s">%s</a></td><td>%s</td></tr>' % (fname,fname,sfsize))
539 for logfname in logfiles: # expect at least tlog - if more
540 if os.path.abspath(logfname) == os.path.abspath(self.tlog): # handled later
541 sectionname = 'All tool run'
542 if (len(logfiles) > 1):
543 sectionname = 'Other'
544 ourpdfs = pdflist
545 else:
546 realname = os.path.basename(logfname)
547 sectionname = os.path.splitext(realname)[0].split('_')[0] # break in case _ added to log
548 ourpdfs = [x for x in pdflist if os.path.basename(x[0]).split('_')[0] == sectionname]
549 pdflist = [x for x in pdflist if os.path.basename(x[0]).split('_')[0] <> sectionname] # remove
550 nacross = 1
551 npdf = len(ourpdfs)
552
553 if npdf > 0:
554 nacross = math.sqrt(npdf) ## int(round(math.log(npdf,2)))
555 if int(nacross)**2 != npdf:
556 nacross += 1
557 nacross = int(nacross)
558 width = min(400,int(1200/nacross))
559 html.append('<div class="toolFormTitle">%s images and outputs</div>' % sectionname)
560 html.append('(Click on a thumbnail image to download the corresponding original PDF image)<br/>')
561 ntogo = nacross # counter for table row padding with empty cells
562 html.append('<div><table class="simple" cellpadding="2" cellspacing="2">\n<tr>')
563 for i,paths in enumerate(ourpdfs):
564 fname,thumb = paths
565 s= """<td><a href="%s"><img src="%s" title="Click to download a PDF of %s" hspace="5" width="%d"
566 alt="Image called %s"/></a></td>\n""" % (fname,thumb,fname,width,fname)
567 if ((i+1) % nacross == 0):
568 s += '</tr>\n'
569 ntogo = 0
570 if i < (npdf - 1): # more to come
571 s += '<tr>'
572 ntogo = nacross
573 else:
574 ntogo -= 1
575 html.append(s)
576 if html[-1].strip().endswith('</tr>'):
577 html.append('</table></div>\n')
578 else:
579 if ntogo > 0: # pad
580 html.append('<td>&nbsp;</td>'*ntogo)
581 html.append('</tr></table></div>\n')
582 logt = open(logfname,'r').readlines()
583 logtext = [x for x in logt if x.strip() > '']
584 html.append('<div class="toolFormTitle">%s log output</div>' % sectionname)
585 if len(logtext) > 1:
586 html.append('\n<pre>\n')
587 html += logtext
588 html.append('\n</pre>\n')
589 else:
590 html.append('%s is empty<br/>' % logfname)
591 if len(fhtml) > 0:
592 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')
593 fhtml.append('</table></div><br/>')
594 html.append('<div class="toolFormTitle">All output files available for downloading</div>\n')
595 html += fhtml # add all non-pdf files to the end of the display
596 else:
597 html.append('<div class="warningmessagelarge">### Error - %s returned no files - please confirm that parameters are sane</div>' % self.opts.interpreter)
598 html.append(galhtmlpostfix)
599 htmlf = file(self.opts.output_html,'w')
600 htmlf.write('\n'.join(html))
601 htmlf.write('\n')
602 htmlf.close()
603 self.html = html
604
605
606 def run(self):
607 """
608 scripts must be small enough not to fill the pipe!
609 """
610 if self.treatbashSpecial and self.opts.interpreter in ['bash','sh']:
611 retval = self.runBash()
612 else:
613 if self.opts.output_dir:
614 ste = open(self.elog,'w')
615 sto = open(self.tlog,'w')
616 sto.write('## Toolfactory generated command line = %s\n' % ' '.join(self.cl))
617 sto.flush()
618 p = subprocess.Popen(self.cl,shell=False,stdout=sto,stderr=ste,stdin=subprocess.PIPE,cwd=self.opts.output_dir)
619 else:
620 p = subprocess.Popen(self.cl,shell=False,stdin=subprocess.PIPE)
621 p.stdin.write(self.script)
622 p.stdin.close()
623 retval = p.wait()
624 if self.opts.output_dir:
625 sto.close()
626 ste.close()
627 err = open(self.elog,'r').readlines()
628 if retval <> 0 and err: # problem
629 print >> sys.stderr,err
630 if self.opts.make_HTML:
631 self.makeHtml()
632 return retval
633
634 def runBash(self):
635 """
636 cannot use - for bash so use self.sfile
637 """
638 if self.opts.output_dir:
639 s = '## Toolfactory generated command line = %s\n' % ' '.join(self.cl)
640 sto = open(self.tlog,'w')
641 sto.write(s)
642 sto.flush()
643 p = subprocess.Popen(self.cl,shell=False,stdout=sto,stderr=sto,cwd=self.opts.output_dir)
644 else:
645 p = subprocess.Popen(self.cl,shell=False)
646 retval = p.wait()
647 if self.opts.output_dir:
648 sto.close()
649 if self.opts.make_HTML:
650 self.makeHtml()
651 return retval
652
653
654 def main():
655 u = """
656 This is a Galaxy wrapper. It expects to be called by a special purpose tool.xml as:
657 <command interpreter="python">rgBaseScriptWrapper.py --script_path "$scriptPath" --tool_name "foo" --interpreter "Rscript"
658 </command>
659 """
660 op = optparse.OptionParser()
661 a = op.add_option
662 a('--script_path',default=None)
663 a('--tool_name',default=None)
664 a('--interpreter',default=None)
665 a('--output_dir',default='./')
666 a('--output_html',default=None)
667 a('--input_tab',default="None")
668 a('--output_tab',default="None")
669 a('--user_email',default='Unknown')
670 a('--bad_user',default=None)
671 a('--make_Tool',default=None)
672 a('--make_HTML',default=None)
673 a('--help_text',default=None)
674 a('--tool_desc',default=None)
675 a('--new_tool',default=None)
676 a('--tool_version',default=None)
677 a('--include_dependencies',default=None)
678 opts, args = op.parse_args()
679 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)
680 assert opts.tool_name,'## Tool Factory expects a tool name - eg --tool_name=DESeq'
681 assert opts.interpreter,'## Tool Factory wrapper expects an interpreter - eg --interpreter=Rscript'
682 assert os.path.isfile(opts.script_path),'## Tool Factory wrapper expects a script path - eg --script_path=foo.R'
683 if opts.output_dir:
684 try:
685 os.makedirs(opts.output_dir)
686 except:
687 pass
688 r = ScriptRunner(opts)
689 if opts.make_Tool:
690 retcode = r.makeTooltar()
691 else:
692 retcode = r.run()
693 os.unlink(r.sfile)
694 if retcode:
695 sys.exit(retcode) # indicate failure to job runner
696
697
698 if __name__ == "__main__":
699 main()
700
701