Mercurial > repos > fubar > brokenandnotdeletablebyowneroradmin
comparison rgToolFactory.py @ 7:7221619caefa
Updated name and added crude gzip generator for toolshed
TODO: add tests and new XML tool descriptor as soon as Greg has it nailed down.
| author | ross lazarus ross.lazarus@gmail.com |
|---|---|
| date | Sat, 02 Jun 2012 10:43:08 +1000 |
| parents | |
| children | 220885b2d7ee |
comparison
equal
deleted
inserted
replaced
| 6:78044a3d4a21 | 7:7221619caefa |
|---|---|
| 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 import tarfile | |
| 32 import re | |
| 33 progname = os.path.split(sys.argv[0])[1] | |
| 34 myversion = 'V000.1 May 2012' | |
| 35 verbose = False | |
| 36 debug = False | |
| 37 | |
| 38 | |
| 39 galhtmlprefix = """<?xml version="1.0" encoding="utf-8" ?> | |
| 40 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | |
| 41 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> | |
| 42 <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | |
| 43 <meta name="generator" content="Galaxy %s tool output - see http://g2.trac.bx.psu.edu/" /> | |
| 44 <title></title> | |
| 45 <link rel="stylesheet" href="/static/style/base.css" type="text/css" /> | |
| 46 </head> | |
| 47 <body> | |
| 48 <div class="document"> | |
| 49 """ | |
| 50 galhtmlattr = """<b><a href="http://rgenetics.org">Galaxy Rgenetics Base Script Wrapper based </a> tool output %s run at %s</b><br/>""" | |
| 51 galhtmlpostfix = """</div></body></html>\n""" | |
| 52 | |
| 53 def timenow(): | |
| 54 """return current time as a string | |
| 55 """ | |
| 56 return time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(time.time())) | |
| 57 # characters that are allowed but need to be escaped | |
| 58 mapped_chars = { '>' :'__gt__', | |
| 59 '<' :'__lt__', | |
| 60 "'" :'__sq__', | |
| 61 '"' :'__dq__', | |
| 62 '{' :'__oc__', | |
| 63 '}' :'__cc__', | |
| 64 '@' : '__at__', | |
| 65 '\n' : '__cn__', | |
| 66 '\r' : '__cr__', | |
| 67 '\t' : '__tc__', | |
| 68 '#' : '__pd__', | |
| 69 '[' :'__ob__', | |
| 70 ']' :'__cb__', | |
| 71 '\t' : 'Xt', | |
| 72 'systemCallsAreNotAllowed' : 'system' | |
| 73 } | |
| 74 | |
| 75 def restore_text(text): | |
| 76 """Restores sanitized text""" | |
| 77 if not text: | |
| 78 return text | |
| 79 for key, value in mapped_chars.items(): | |
| 80 text = text.replace(value, key) | |
| 81 return text | |
| 82 | |
| 83 class ScriptRunner: | |
| 84 """class is a wrapper for an arbitrary script | |
| 85 """ | |
| 86 | |
| 87 def __init__(self,opts=None): | |
| 88 """ | |
| 89 run the script | |
| 90 cheetah/galaxy will provide an escaped string so | |
| 91 __pd__ your script goes here | |
| 92 __cr____cn__ourargs __lt__- commandArgs(TRUE) | |
| 93 __cr____cn__inf = ourargs[1] | |
| 94 __cr____cn__outf = ourargs[2] | |
| 95 __cr____cn__inp = read.table(inf,head=T,rownames=F,sep=__sq__Xt__sq__) | |
| 96 __cr____cn__ write.table(inp,outf, quote=FALSE, sep=__dq__Xt__dq__,row.names=F) | |
| 97 __cr____cn__sessionInfo() | |
| 98 __cr____cn__ | |
| 99 """ | |
| 100 self.myname = sys.argv[0] # get our name because we write ourselves out as a tool later | |
| 101 self.thumbformat = 'jpg' | |
| 102 self.opts = opts | |
| 103 self.toolname = re.sub('[^a-zA-Z0-9_]+', '', opts.tool_name) | |
| 104 s = open(self.opts.script_path,'r').read() | |
| 105 self.script = restore_text(s) | |
| 106 self.pyfile = self.myname | |
| 107 self.xmlfile = '%s.xml' % os.path.splitext(self.pyfile)[0] # punt | |
| 108 self.sfile = '%s.%s' % (self.toolname,opts.interpreter) | |
| 109 localscript = open(self.sfile,'w') | |
| 110 localscript.write(self.script) | |
| 111 localscript.close() | |
| 112 if opts.output_dir or self.opts.makeTool: # may not want these complexities if a simple script | |
| 113 self.tlog = os.path.join(opts.output_dir,"%s_runner.log" % self.toolname) | |
| 114 artifactpath = os.path.join(opts.output_dir,'%s_run.script' % self.toolname) | |
| 115 artifact = open(artifactpath,'w') | |
| 116 artifact.write(self.script) | |
| 117 artifact.write('\n') | |
| 118 artifact.close() | |
| 119 self.cl = [] | |
| 120 self.html = [] | |
| 121 a = self.cl.append | |
| 122 a(opts.interpreter) | |
| 123 a('-') # use stdin | |
| 124 a(opts.input_tab) | |
| 125 a(opts.output_tab) | |
| 126 | |
| 127 def makeTooltar(self): | |
| 128 """ | |
| 129 a tool is a gz tarball with eg | |
| 130 /toolname/tool.xml /toolname/tool.py /toolname/test-data/test1_in.foo ... | |
| 131 """ | |
| 132 retval = self.run() | |
| 133 if retval: | |
| 134 print >> sys.stderr,'## Run failed. Cannot build yet. Please fix and retry' | |
| 135 sys.exit(1) | |
| 136 tarpath = os.path.join(self.opts.output_dir,"%s.gz" % self.toolname) | |
| 137 tar = tarfile.open(tarpath, "w:gz") | |
| 138 tar.add(self.xmlfile,arcname='%s.xml' % self.toolname) | |
| 139 tar.add(self.pyfile,arcname=os.path.basename(self.pyfile)) | |
| 140 tar.add(self.sfile,arcname=self.sfile) | |
| 141 tar.close() | |
| 142 self.makeHtml() | |
| 143 return retval | |
| 144 | |
| 145 def compressPDF(self,inpdf=None,thumbformat='png'): | |
| 146 """need absolute path to pdf | |
| 147 """ | |
| 148 assert os.path.isfile(inpdf), "## Input %s supplied to %s compressPDF not found" % (inpdf,self.myName) | |
| 149 hf,hlog = tempfile.mkstemp(suffix="%s.log" % self.toolname) | |
| 150 sto = open(hlog,'w') | |
| 151 outpdf = '%s_compressed' % inpdf | |
| 152 cl = ["gs", "-sDEVICE=pdfwrite", "-dNOPAUSE", "-dBATCH", "-sOutputFile=%s" % outpdf,inpdf] | |
| 153 x = subprocess.Popen(cl,stdout=sto,stderr=sto,cwd=self.opts.output_dir) | |
| 154 retval1 = x.wait() | |
| 155 if retval1 == 0: | |
| 156 os.unlink(inpdf) | |
| 157 shutil.move(outpdf,inpdf) | |
| 158 outpng = '%s.%s' % (os.path.splitext(inpdf)[0],thumbformat) | |
| 159 cl2 = ['convert', inpdf, outpng] | |
| 160 x = subprocess.Popen(cl2,stdout=sto,stderr=sto,cwd=self.opts.output_dir) | |
| 161 retval2 = x.wait() | |
| 162 sto.close() | |
| 163 retval = retval1 or retval2 | |
| 164 return retval | |
| 165 | |
| 166 | |
| 167 def getfSize(self,fpath,outpath): | |
| 168 """ | |
| 169 format a nice file size string | |
| 170 """ | |
| 171 size = '' | |
| 172 fp = os.path.join(outpath,fpath) | |
| 173 if os.path.isfile(fp): | |
| 174 n = float(os.path.getsize(fp)) | |
| 175 if n > 2**20: | |
| 176 size = ' (%1.1f MB)' % (n/2**20) | |
| 177 elif n > 2**10: | |
| 178 size = ' (%1.1f KB)' % (n/2**10) | |
| 179 elif n > 0: | |
| 180 size = ' (%d B)' % (int(n)) | |
| 181 return size | |
| 182 | |
| 183 def makeHtml(self): | |
| 184 """ | |
| 185 """ | |
| 186 flist = os.listdir(self.opts.output_dir) | |
| 187 flist = [x for x in flist if x <> 'Rplots.pdf'] | |
| 188 flist.sort() | |
| 189 html = [galhtmlprefix % progname,] | |
| 190 html.append('<h2>Galaxy %s outputs run at %s</h2><br/>\n' % (self.toolname,timenow())) | |
| 191 fhtml = [] | |
| 192 if len(flist) > 0: | |
| 193 html.append('<table cellpadding="3" cellspacing="3">\n') | |
| 194 for fname in flist: | |
| 195 dname,e = os.path.splitext(fname) | |
| 196 sfsize = self.getfSize(fname,self.opts.output_dir) | |
| 197 if e.lower() == '.pdf' : # compress and make a thumbnail | |
| 198 thumb = '%s.%s' % (dname,self.thumbformat) | |
| 199 pdff = os.path.join(self.opts.output_dir,fname) | |
| 200 retval = self.compressPDF(inpdf=pdff,thumbformat=self.thumbformat) | |
| 201 if retval == 0: | |
| 202 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) | |
| 203 html.append(s) | |
| 204 fhtml.append('<li><a href="%s">%s %s</a></li>' % (fname,fname,sfsize)) | |
| 205 else: | |
| 206 fhtml.append('<li><a href="%s">%s %s</a></li>' % (fname,fname,sfsize)) | |
| 207 html.append('</table>\n') | |
| 208 if len(fhtml) > 0: | |
| 209 fhtml.insert(0,'<ul>') | |
| 210 fhtml.append('</ul>') | |
| 211 html += fhtml # add all non-pdf files to the end of the display | |
| 212 else: | |
| 213 html.append('<h2>### Error - %s returned no files - please confirm that parameters are sane</h1>' % self.opts.interpreter) | |
| 214 html.append('<h3>%s log follows below</h3><hr><pre>\n' % self.opts.interpreter) | |
| 215 rlog = open(self.tlog,'r').readlines() | |
| 216 html += rlog | |
| 217 html.append('%s CL = %s</br>\n' % (self.toolname,' '.join(sys.argv))) | |
| 218 html.append('CL = %s</br>\n' % (' '.join(self.cl))) | |
| 219 html.append('</pre>\n') | |
| 220 html.append(galhtmlattr % (progname,timenow())) | |
| 221 html.append(galhtmlpostfix) | |
| 222 htmlf = file(self.opts.output_html,'w') | |
| 223 htmlf.write('\n'.join(html)) | |
| 224 htmlf.write('\n') | |
| 225 htmlf.close() | |
| 226 self.html = html | |
| 227 | |
| 228 | |
| 229 def run(self): | |
| 230 """ | |
| 231 """ | |
| 232 if self.opts.output_dir or self.opts.makeTool: | |
| 233 sto = open(self.tlog,'w') | |
| 234 p = subprocess.Popen(' '.join(self.cl),shell=True,stdout=sto,stderr=sto,stdin=subprocess.PIPE,cwd=self.opts.output_dir) | |
| 235 else: | |
| 236 p = subprocess.Popen(' '.join(self.cl),shell=True,stdin=subprocess.PIPE) | |
| 237 p.stdin.write(self.script) | |
| 238 p.stdin.close() | |
| 239 retval = p.wait() | |
| 240 if self.opts.output_dir or self.opts.makeTool: | |
| 241 sto.close() | |
| 242 self.makeHtml() | |
| 243 return retval | |
| 244 | |
| 245 | |
| 246 def main(): | |
| 247 u = """ | |
| 248 This is a Galaxy wrapper. It expects to be called by a special purpose tool.xml as: | |
| 249 <command interpreter="python">rgBaseScriptWrapper.py --script_path "$scriptPath" --tool_name "foo" --interpreter "Rscript" | |
| 250 </command> | |
| 251 """ | |
| 252 op = optparse.OptionParser() | |
| 253 a = op.add_option | |
| 254 a('--script_path',default=None) | |
| 255 a('--tool_name',default=None) | |
| 256 a('--interpreter',default=None) | |
| 257 a('--output_dir',default=None) | |
| 258 a('--output_html',default=None) | |
| 259 a('--input_tab',default='NONE') | |
| 260 a('--output_tab',default='NONE') | |
| 261 a('--user_email',default=None) | |
| 262 a('--bad_user',default=None) | |
| 263 a('--makeTool',default=None) | |
| 264 opts, args = op.parse_args() | |
| 265 assert not opts.bad_user,'%s is NOT authorized to use this tool. Please ask your friendly admin' % opts.bad_user | |
| 266 assert opts.tool_name,'## Tool Factory expects a tool name - eg --tool_name=DESeq' | |
| 267 assert opts.interpreter,'## Tool Factory wrapper expects an interpreter - eg --interpreter=Rscript' | |
| 268 assert os.path.isfile(opts.script_path),'## Tool Factory wrapper expects a script path - eg --script_path=foo.R' | |
| 269 if opts.output_dir: | |
| 270 try: | |
| 271 os.makedirs(opts.output_dir) | |
| 272 except: | |
| 273 pass | |
| 274 r = ScriptRunner(opts) | |
| 275 if opts.makeTool: | |
| 276 retcode = r.makeTooltar() | |
| 277 else: | |
| 278 retcode = r.run() | |
| 279 if retcode: | |
| 280 sys.exit(retcode) # indicate failure to job runner | |
| 281 | |
| 282 | |
| 283 if __name__ == "__main__": | |
| 284 main() | |
| 285 | |
| 286 |
