comparison rgDynamicScriptWrapper.py @ 0:fda8032fe989

Initial checkin of dynamic script runner. Goal is to add code to generate a new toolshed entry once the script works correctly
author ross lazarus ross.lazarus@gmail.com
date Wed, 30 May 2012 22:36:34 +1000
parents
children 0133b97e477e
comparison
equal deleted inserted replaced
-1:000000000000 0:fda8032fe989
1 # rgDynamicScriptWrapper.py
2 # derived from
3 # rgBaseScriptWrapper.py
4 # to run some user supplied code
5 # extremely dangerous
6 # trusted users only - private site only
7 # a list in the xml is searched - only users in the list can run this tool.
8 #
9 # copyright ross lazarus (ross.lazarus@gmail.com) May 2012
10 #
11 # all rights reserved
12 # Licensed under the LGPL for your pleasure
13 # Derived from rgDGE.py in May 2012
14 # generalized to run required interpreter
15 # to make your own tools based on a given script and interpreter such as perl or python
16 # clone this and the corresponding xml wrapper
17 # replace the parameters/inputs/outputs and the configfile contents with your script
18 # Use the $foo syntax to place your parameter values inside the script to assign them - at run time, the script will be used as a template
19 # and returned as part of the output to the user - with the right values for all the parameters.
20 # Note that this assumes you want all the outputs arranged as a single Html file output
21 # after this generic script runner runs your script with the specified interpreter,
22 # it will collect all output files into the specified output_html, making thumbnails for all the pdfs it finds and making links for all the other files.
23
24 import sys
25 import shutil
26 import subprocess
27 import os
28 import time
29 import tempfile
30 import optparse
31
32 progname = os.path.split(sys.argv[0])[1]
33 myversion = 'V000.1 May 2012'
34 verbose = False
35 debug = False
36
37 # characters that are allowed but need to be escaped
38 # also a test sandboxing of any R system commands
39 # ultimately futile - we need to generate a new tool
40 # which will have no new security problems!
41 mapped_chars = { '>' :'__gt__',
42 '<' :'__lt__',
43 "'" :'__sq__',
44 '"' :'__dq__',
45 '{' :'__oc__',
46 '}' :'__cc__',
47 '@' : '__at__',
48 '\n' : '__cn__',
49 '\r' : '__cr__',
50 '\t' : '__tc__',
51 '#' : '__pd__',
52 '[' :'__ob__',
53 ']' :'__cb__',
54 '\t' : 'Xt',
55 'systemCallsAreNotAllowed' : 'system'
56 }
57
58 galhtmlprefix = """<?xml version="1.0" encoding="utf-8" ?>
59 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
60 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
61 <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
62 <meta name="generator" content="Galaxy %s tool output - see http://g2.trac.bx.psu.edu/" />
63 <title></title>
64 <link rel="stylesheet" href="/static/style/base.css" type="text/css" />
65 </head>
66 <body>
67 <div class="document">
68 """
69 galhtmlattr = """<b><a href="http://rgenetics.org">Galaxy Rgenetics Base Script Wrapper based </a> tool output %s run at %s</b><br/>"""
70 galhtmlpostfix = """</div></body></html>\n"""
71
72 def timenow():
73 """return current time as a string
74 """
75 return time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(time.time()))
76
77 def restore_text(text):
78 """Restores sanitized text"""
79 if not text:
80 return text
81 for key, value in mapped_chars.items():
82 text = text.replace(value, key)
83 return text
84
85 class ScriptRunner:
86 """class is a wrapper for an arbitrary script
87 """
88
89 def __init__(self,opts=None):
90 """
91 run the script
92 cheetah/galaxy will provide an escaped string so
93 __pd__ your script goes here
94 __cr____cn__ourargs __lt__- commandArgs(TRUE)
95 __cr____cn__inf = ourargs[1]
96 __cr____cn__outf = ourargs[2]
97 __cr____cn__inp = read.table(inf,head=T,rownames=F,sep=__sq__Xt__sq__)
98 __cr____cn__ write.table(inp,outf, quote=FALSE, sep=__dq__Xt__dq__,row.names=F)
99 __cr____cn__sessionInfo()
100 __cr____cn__
101 """
102 self.thumbformat = 'jpg'
103 self.opts = opts
104 self.toolname = opts.tool_name.replace(' ','_')
105 s = open(self.opts.script_path,'r').read()
106 self.script = restore_text(s)
107 if opts.output_dir: # may not want these complexities if a simple script
108 self.tlog = os.path.join(opts.output_dir,"%s_runner.log" % self.toolname)
109 artifactpath = os.path.join(opts.output_dir,'%s_run.script' % self.toolname)
110 artifact = open(artifactpath,'w')
111 artifact.write(self.script)
112 artifact.write('\n')
113 artifact.close()
114 self.cl = []
115 a = self.cl.append
116 a(opts.interpreter)
117 a('-') # use stdin
118 a(opts.input_tab)
119 a(opts.output_tab)
120
121 def compressPDF(self,inpdf=None,thumbformat='png'):
122 """need absolute path to pdf
123 """
124 assert os.path.isfile(inpdf), "## Input %s supplied to %s compressPDF not found" % (inpdf,self.myName)
125 hf,hlog = tempfile.mkstemp(suffix="%s.log" % self.toolname)
126 sto = open(hlog,'w')
127 outpdf = '%s_compressed' % inpdf
128 cl = ["gs", "-sDEVICE=pdfwrite", "-dNOPAUSE", "-dBATCH", "-sOutputFile=%s" % outpdf,inpdf]
129 x = subprocess.Popen(cl,stdout=sto,stderr=sto,cwd=self.opts.output_dir)
130 retval1 = x.wait()
131 if retval1 == 0:
132 os.unlink(inpdf)
133 shutil.move(outpdf,inpdf)
134 outpng = '%s.%s' % (os.path.splitext(inpdf)[0],thumbformat)
135 cl2 = ['convert', inpdf, outpng]
136 x = subprocess.Popen(cl2,stdout=sto,stderr=sto,cwd=self.opts.output_dir)
137 retval2 = x.wait()
138 sto.close()
139 retval = retval1 or retval2
140 return retval
141
142
143 def getfSize(self,fpath,outpath):
144 """
145 format a nice file size string
146 """
147 size = ''
148 fp = os.path.join(outpath,fpath)
149 if os.path.isfile(fp):
150 n = float(os.path.getsize(fp))
151 if n > 2**20:
152 size = ' (%1.1f MB)' % (n/2**20)
153 elif n > 2**10:
154 size = ' (%1.1f KB)' % (n/2**10)
155 elif n > 0:
156 size = ' (%d B)' % (int(n))
157 return size
158
159
160 def run(self):
161 """
162 """
163 if self.opts.output_dir:
164 sto = open(self.tlog,'w')
165 p = subprocess.Popen(' '.join(self.cl),shell=True,stdout=sto,stderr=sto,stdin=subprocess.PIPE,cwd=self.opts.output_dir)
166 else:
167 p = subprocess.Popen(' '.join(self.cl),shell=True,stdin=subprocess.PIPE)
168 p.stdin.write(self.script)
169 p.stdin.close()
170 retval = p.wait()
171 if self.opts.output_dir:
172 sto.close()
173 flist = os.listdir(self.opts.output_dir)
174 flist = [x for x in flist if x <> 'Rplots.pdf']
175 flist.sort()
176 html = [galhtmlprefix % progname,]
177 html.append('<h2>Galaxy %s outputs run at %s</h2></br>Click on a thumbnail below to download the original PDF</br>\n' % (self.toolname,timenow()))
178 fhtml = []
179 if len(flist) > 0:
180 html.append('<table cellpadding="3" cellspacing="3">\n')
181 for fname in flist:
182 dname,e = os.path.splitext(fname)
183 sfsize = self.getfSize(fname,self.opts.output_dir)
184 if e.lower() == '.pdf' : # compress and make a thumbnail
185 thumb = '%s.%s' % (dname,self.thumbformat)
186 pdff = os.path.join(self.opts.output_dir,fname)
187 retval = self.compressPDF(inpdf=pdff,thumbformat=self.thumbformat)
188 if retval == 0:
189 s= '<tr><td><a href="%s"><img src="%s" title="Click to download a PDF of %s" hspace="10" width="600"></a></td></tr>\n' % (fname,thumb,fname)
190 html.append(s)
191 fhtml.append('<li><a href="%s">%s %s</a></li>' % (fname,fname,sfsize))
192 else:
193 fhtml.append('<li><a href="%s">%s %s</a></li>' % (fname,fname,sfsize))
194 html.append('</table>\n')
195 if len(fhtml) > 0:
196 fhtml.insert(0,'<ul>')
197 fhtml.append('</ul>')
198 html += fhtml # add all non-pdf files to the end of the display
199 else:
200 html.append('<h2>### Error - %s returned no files - please confirm that parameters are sane</h1>' % self.opts.interpreter)
201 html.append('<h3>%s log follows below</h3><hr><pre>\n' % self.opts.interpreter)
202 rlog = open(self.tlog,'r').readlines()
203 html += rlog
204 html.append('%s CL = %s</br>\n' % (self.toolname,' '.join(sys.argv)))
205 html.append('CL = %s</br>\n' % (' '.join(self.cl)))
206 html.append('</pre>\n')
207 html.append(galhtmlattr % (progname,timenow()))
208 html.append(galhtmlpostfix)
209 htmlf = file(self.opts.output_html,'w')
210 htmlf.write('\n'.join(html))
211 htmlf.write('\n')
212 htmlf.close()
213 return retval
214
215
216 def main():
217 u = """
218 This is a Galaxy wrapper. It expects to be called by a special purpose tool.xml as:
219 <command interpreter="python">rgBaseScriptWrapper.py --script_path "$scriptPath" --tool_name "foo" --interpreter "Rscript"
220 </command>
221 """
222 permitted_users = ['rlazarus@bakeridi.edu.au','akaspi@bakeridi.edu.au','mziemann@bakeridi.edu.edu']
223 op = optparse.OptionParser()
224 a = op.add_option
225 a('--script_path',default=None)
226 a('--tool_name',default=None)
227 a('--interpreter',default=None)
228 a('--output_dir',default=None)
229 a('--output_html',default=None)
230 a('--input_tab',default='NONE')
231 a('--output_tab',default='NONE')
232 a('--user_email',default=None)
233 a('--bad_user',default=None)
234 opts, args = op.parse_args()
235 assert not opts.bad_user,'%s is NOT authorized to use this tool. Please ask your friendly admin' % opts.bad_user
236 assert opts.tool_name,'## Dynamic script wrapper expects a tool name - eg --tool_name=DESeq'
237 assert opts.interpreter,'## Dynamic script wrapper expects an interpreter - eg --interpreter=Rscript'
238 assert os.path.isfile(opts.script_path),'## Dynamic script wrapper expects a script path - eg --script_path=foo.R'
239 if opts.output_dir:
240 try:
241 os.makedirs(opts.output_dir)
242 except:
243 pass
244 r = ScriptRunner(opts)
245 retcode = r.run()
246 if retcode:
247 sys.exit(retcode) # indicate failure to job runner
248
249
250 if __name__ == "__main__":
251 main()
252
253