Mercurial > repos > fubar > tool_factory_2
comparison rgToolFactory2.py @ 15:dd6cf2ddaac7 draft
Uploaded
| author | fubar | 
|---|---|
| date | Wed, 28 Jan 2015 19:28:32 -0500 | 
| parents | 3635f4518c4d | 
| children | 
   comparison
  equal
  deleted
  inserted
  replaced
| 14:3635f4518c4d | 15:dd6cf2ddaac7 | 
|---|---|
| 6 # all rights reserved | 6 # all rights reserved | 
| 7 # Licensed under the LGPL | 7 # Licensed under the LGPL | 
| 8 # suggestions for improvement and bug fixes welcome at https://bitbucket.org/fubar/galaxytoolfactory/wiki/Home | 8 # suggestions for improvement and bug fixes welcome at https://bitbucket.org/fubar/galaxytoolfactory/wiki/Home | 
| 9 # | 9 # | 
| 10 # January 2015 | 10 # January 2015 | 
| 11 # unified all setups by passing the script on the cl rather than via a PIPE - no need for treat_bash_special so removed | |
| 12 # | |
| 11 # in the process of building a complex tool | 13 # in the process of building a complex tool | 
| 12 # added ability to choose one of the current toolshed package_r or package_perl or package_python dependencies and source that package | 14 # added ability to choose one of the current toolshed package_r or package_perl or package_python dependencies and source that package | 
| 13 # add that package to tool_dependencies | 15 # add that package to tool_dependencies | 
| 14 # Note that once the generated tool is loaded, it will have that package's env.sh loaded automagically so there is no | 16 # Note that once the generated tool is loaded, it will have that package's env.sh loaded automagically so there is no | 
| 15 # --envshpath in the parameters for the generated tool and it uses the system one which will be first on the adjusted path. | 17 # --envshpath in the parameters for the generated tool and it uses the system one which will be first on the adjusted path. | 
| 118 def timenow(): | 120 def timenow(): | 
| 119 """return current time as a string | 121 """return current time as a string | 
| 120 """ | 122 """ | 
| 121 return time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(time.time())) | 123 return time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(time.time())) | 
| 122 | 124 | 
| 125 def quote_non_numeric(s): | |
| 126 """return a prequoted string for non-numerics | |
| 127 useful for perl and Rscript parameter passing? | |
| 128 """ | |
| 129 try: | |
| 130 res = float(s) | |
| 131 return s | |
| 132 except ValueError: | |
| 133 return '"%s"' % s | |
| 134 | |
| 123 html_escape_table = { | 135 html_escape_table = { | 
| 124 "&": "&", | 136 "&": "&", | 
| 125 ">": ">", | 137 ">": ">", | 
| 126 "<": "<", | 138 "<": "<", | 
| 127 "$": "\$" | 139 "$": "\$" | 
| 152 else: | 164 else: | 
| 153 citation_tuples.append( ("bibtex", citation[len("bibtex"):].strip() ) ) | 165 citation_tuples.append( ("bibtex", citation[len("bibtex"):].strip() ) ) | 
| 154 return citation_tuples | 166 return citation_tuples | 
| 155 | 167 | 
| 156 def shell_source(script): | 168 def shell_source(script): | 
| 157 """need a way to source a Galaxy tool interpreter env.sh so we can use that dependency | 169 """need a way to source a Galaxy tool interpreter env.sh to point at the right dependency package | 
| 158 package | 170 This based on the idea in http://pythonwise.blogspot.fr/2010/04/sourcing-shell-script.html | 
| 159 see http://pythonwise.blogspot.fr/2010/04/sourcing-shell-script.html | 171 Note that we have to finesse any wierdly quoted newlines in automagic exports using nulls (env -0) as newlines""" | 
| 160 Sometime you want to emulate the action of "source" in bash, | |
| 161 settings some environment variables. Here is a way to do it. | |
| 162 Note that we have to finesse the automagic exports using nulls as newlines for env""" | |
| 163 pipe = subprocess.Popen("env -i ; . %s ; env -0" % script, stdout=subprocess.PIPE, shell=True) | 172 pipe = subprocess.Popen("env -i ; . %s ; env -0" % script, stdout=subprocess.PIPE, shell=True) | 
| 164 output = pipe.communicate()[0] | 173 output = pipe.communicate()[0] | 
| 165 outl = output.split('\0') | 174 outl = output.split('\0') | 
| 166 outl = [x for x in outl if len(x.split("=")) == 2] | 175 outl = [x for x in outl if len(x.split("=")) == 2] | 
| 167 newenv = dict((line.split("=", 1) for line in outl)) | 176 newenv = dict((line.split("=", 1) for line in outl)) | 
| 174 little overhead... | 183 little overhead... | 
| 175 | 184 | 
| 176 """ | 185 """ | 
| 177 | 186 | 
| 178 | 187 | 
| 179 def __init__(self,opts=None,treatbashSpecial=True): | 188 def __init__(self,opts=None): | 
| 180 """ | 189 """ | 
| 181 cleanup inputs, setup some outputs | 190 cleanup inputs, setup some outputs | 
| 182 | 191 | 
| 183 """ | 192 """ | 
| 184 | 193 | 
| 311 | 320 | 
| 312 self.useGM = cmd_exists('gm') | 321 self.useGM = cmd_exists('gm') | 
| 313 self.useIM = cmd_exists('convert') | 322 self.useIM = cmd_exists('convert') | 
| 314 self.useGS = cmd_exists('gs') | 323 self.useGS = cmd_exists('gs') | 
| 315 self.temp_warned = False # we want only one warning if $TMP not set | 324 self.temp_warned = False # we want only one warning if $TMP not set | 
| 316 self.treatbashSpecial = treatbashSpecial | |
| 317 if opts.output_dir: # simplify for the tool tarball | 325 if opts.output_dir: # simplify for the tool tarball | 
| 318 os.chdir(opts.output_dir) | 326 os.chdir(opts.output_dir) | 
| 319 self.thumbformat = 'png' | 327 self.thumbformat = 'png' | 
| 320 self.opts = opts | 328 self.opts = opts | 
| 321 self.toolname = re.sub('[^a-zA-Z0-9_]+', '', opts.tool_name) # a sanitizer now does this but.. | 329 self.toolname = re.sub('[^a-zA-Z0-9_]+', '', opts.tool_name) # a sanitizer now does this but.. | 
| 378 psplit = p.split(',') | 386 psplit = p.split(',') | 
| 379 param = html_unescape(psplit[0]) | 387 param = html_unescape(psplit[0]) | 
| 380 value = html_unescape(psplit[1]) | 388 value = html_unescape(psplit[1]) | 
| 381 a('%s="%s"' % (param,value)) | 389 a('%s="%s"' % (param,value)) | 
| 382 if (self.opts.interpreter == 'Rscript'): | 390 if (self.opts.interpreter == 'Rscript'): | 
| 383 # pass params on command line | 391 # pass params on command line as expressions which the script evaluates - see sample | 
| 384 if self.opts.input_tab: | 392 if self.opts.input_tab: | 
| 385 a('INPATHS="%s"' % self.infile_paths) | 393 a('INPATHS="%s"' % self.infile_paths) | 
| 386 a('INNAMES="%s"' % self.infile_names) | 394 a('INNAMES="%s"' % self.infile_names) | 
| 387 if self.opts.output_tab: | 395 if self.opts.output_tab: | 
| 388 a('OUTPATH="%s"' % self.opts.output_tab) | 396 a('OUTPATH="%s"' % self.opts.output_tab) | 
| 389 for p in opts.additional_parameters: | 397 for p in opts.additional_parameters: | 
| 390 p = p.replace('"','') | 398 p = p.replace('"','') | 
| 391 psplit = p.split(',') | 399 psplit = p.split(',') | 
| 392 param = html_unescape(psplit[0]) | 400 param = html_unescape(psplit[0]) | 
| 393 value = html_unescape(psplit[1]) | 401 value = html_unescape(psplit[1]) | 
| 394 a('%s="%s"' % (param,value)) | 402 a('%s=%s' % (param,quote_non_numeric(value))) | 
| 395 if (self.opts.interpreter == 'perl'): | 403 if (self.opts.interpreter == 'perl'): | 
| 396 # pass params on command line | 404 # pass positional params on command line - perl script needs to discombobulate the path/name lists | 
| 397 if self.opts.input_tab: | 405 if self.opts.input_tab: | 
| 398 a('%s' % self.infile_paths) | 406 a('%s' % self.infile_paths) | 
| 399 a('%s' % self.infile_names) | 407 a('%s' % self.infile_names) | 
| 400 if self.opts.output_tab: | 408 if self.opts.output_tab: | 
| 401 a('%s' % self.opts.output_tab) | 409 a('%s' % self.opts.output_tab) | 
| 402 for p in opts.additional_parameters: | 410 for p in opts.additional_parameters: | 
| 411 # followed by any additional name=value parameter pairs | |
| 403 p = p.replace('"','') | 412 p = p.replace('"','') | 
| 404 psplit = p.split(',') | 413 psplit = p.split(',') | 
| 405 param = html_unescape(psplit[0]) | 414 param = html_unescape(psplit[0]) | 
| 406 value = html_unescape(psplit[1]) | 415 value = html_unescape(psplit[1]) | 
| 407 if (value.find(' ') <> -1): | 416 a('%s=%s' % (param,quote_non_numeric(value))) | 
| 408 a('%s="%s"' % (param,value)) | |
| 409 else: | |
| 410 a('%s=%s' % (param,value)) | |
| 411 if self.opts.interpreter == 'sh' or self.opts.interpreter == 'bash': | 417 if self.opts.interpreter == 'sh' or self.opts.interpreter == 'bash': | 
| 412 # more is better - now move all params into environment AND drop on to command line. | 418 # more is better - now move all params into environment AND drop on to command line. | 
| 413 self.cl.insert(0,'env') | 419 self.cl.insert(0,'env') | 
| 414 if self.opts.input_tab: | 420 if self.opts.input_tab: | 
| 415 self.cl.insert(1,'INPATHS=%s' % (self.infile_paths)) | 421 self.cl.insert(1,'INPATHS=%s' % (self.infile_paths)) | 
| 421 # additional params appear in CL - yes, it's confusing | 427 # additional params appear in CL - yes, it's confusing | 
| 422 for i,p in enumerate(opts.additional_parameters): | 428 for i,p in enumerate(opts.additional_parameters): | 
| 423 psplit = p.split(',') | 429 psplit = p.split(',') | 
| 424 param = html_unescape(psplit[0]) | 430 param = html_unescape(psplit[0]) | 
| 425 value = html_unescape(psplit[1]) | 431 value = html_unescape(psplit[1]) | 
| 426 if (value.find(' ') <> -1): | 432 a('%s=%s' % (param,quote_non_numeric(value))) | 
| 427 a('%s="%s"' % (param,value)) | 433 self.cl.insert(4+i,'%s=%s' % (param,quote_non_numeric(value))) | 
| 428 self.cl.insert(4+i,'%s="%s"' % (param,value)) | 434 self.interpreter_owner = 'SYSTEM' | 
| 429 else: | 435 self.interpreter_pack = 'SYSTEM' | 
| 430 a('%s=%s' % (param,value)) | 436 self.interpreter_name = 'SYSTEM' | 
| 431 self.cl.insert(4+i,'%s=%s' % (param,value)) | 437 self.interpreter_version = 'SYSTEM' | 
| 432 self.interp_owner = None | 438 self.interpreter_revision = 'SYSTEM' | 
| 433 self.interp_pack = None | |
| 434 self.interp_revision = None | |
| 435 self.interp_version = None | |
| 436 if opts.envshpath <> 'system': # need to parse out details for our tool_dependency | 439 if opts.envshpath <> 'system': # need to parse out details for our tool_dependency | 
| 437 try: # fragile - depends on common naming convention as at jan 2015 = package_[interp]_v0_v1_v2... = version v0.v1.v2.. is in play | 440 try: # fragile - depends on common naming convention as at jan 2015 = package_[interp]_v0_v1_v2... = version v0.v1.v2.. is in play | 
| 438 | 441 # this ONLY happens at tool generation by an admin - the generated tool always uses the default of system so path is from local env.sh | 
| 439 packdetails = opts.envshpath.split(os.path.sep)[-4:-1] # eg ['fubar', 'package_r_3_1_1', '63cdb9b2234c'] | 442 packdetails = opts.envshpath.split(os.path.sep)[-4:-1] # eg ['fubar', 'package_r_3_1_1', '63cdb9b2234c'] | 
| 440 self.interpreter_owner = packdetails[0] | 443 self.interpreter_owner = packdetails[0] | 
| 441 self.interpreter_pack = packdetails[1] | 444 self.interpreter_pack = packdetails[1] | 
| 442 self.interpreter_name = packdetails[1].split('_')[1].upper() | 445 self.interpreter_name = packdetails[1].split('_')[1].upper() | 
| 443 self.interpreter_revision = packdetails[2] | 446 self.interpreter_revision = packdetails[2] | 
| 629 if self.opts.envshpath == 'system': | 632 if self.opts.envshpath == 'system': | 
| 630 tooldepcontent = self.toolhtmldepskel % readme_dict | 633 tooldepcontent = self.toolhtmldepskel % readme_dict | 
| 631 else: | 634 else: | 
| 632 tooldepcontent = self.toolhtmldepinterpskel % readme_dict | 635 tooldepcontent = self.toolhtmldepinterpskel % readme_dict | 
| 633 else: | 636 else: | 
| 634 tooldepcontent = self.emptytoolhtmldepskel % readme_dictls -l | 637 tooldepcontent = self.emptytoolhtmldepskel % readme_dict | 
| 635 depf = open(os.path.join(tdir,'tool_dependencies.xml'),'w') | 638 depf = open(os.path.join(tdir,'tool_dependencies.xml'),'w') | 
| 636 depf.write(tooldepcontent) | 639 depf.write(tooldepcontent) | 
| 637 depf.write('\n') | 640 depf.write('\n') | 
| 638 depf.close() | 641 depf.close() | 
| 639 testdir = os.path.join(tdir,'test-data') | 642 testdir = os.path.join(tdir,'test-data') | 
| 858 | 861 | 
| 859 | 862 | 
| 860 | 863 | 
| 861 def run(self): | 864 def run(self): | 
| 862 """ | 865 """ | 
| 863 scripts must be small enough not to fill the pipe! | 866 Some devteam tools have this defensive stderr read so I'm keeping with the faith | 
| 867 Feel free to update. | |
| 864 """ | 868 """ | 
| 865 if self.opts.envshpath <> 'system': | 869 if self.opts.envshpath <> 'system': | 
| 866 shell_source(self.opts.envshpath) | 870 shell_source(self.opts.envshpath) | 
| 867 if self.treatbashSpecial and self.opts.interpreter in ['bash','sh']: | 871 # this only happens at tool generation - the generated tool relies on the dependencies all being set up | 
| 868 retval = self.runBash() | 872 # at toolshed installation by sourcing local env.sh | 
| 869 else: | 873 if self.opts.output_dir: | 
| 870 if self.opts.output_dir: | 874 ste = open(self.elog,'wb') | 
| 871 ste = open(self.elog,'w') | 875 sto = open(self.tlog,'wb') | 
| 872 sto = open(self.tlog,'w') | 876 s = ' '.join(self.cl) | 
| 873 sto.write('## Toolfactory generated command line = %s\n' % ' '.join(self.cl)) | 877 sto.write('## Executing Toolfactory generated command line = %s\n' % s) | 
| 874 sto.flush() | 878 sto.flush() | 
| 875 p = subprocess.Popen(self.cl,shell=False,stdout=sto,stderr=ste,cwd=self.opts.output_dir) | 879 p = subprocess.Popen(self.cl,shell=False,stdout=sto,stderr=ste,cwd=self.opts.output_dir) | 
| 876 else: | |
| 877 p = subprocess.Popen(self.cl,shell=False) | |
| 878 retval = p.wait() | 880 retval = p.wait() | 
| 879 if self.opts.output_dir: | 881 sto.close() | 
| 880 sto.close() | 882 ste.close() | 
| 881 ste.close() | 883 tmp_stderr = open( self.elog, 'rb' ) | 
| 882 err = open(self.elog,'r').readlines() | 884 err = '' | 
| 883 if retval <> 0 and err: # problem | 885 buffsize = 1048576 | 
| 884 print >> sys.stderr,err | 886 try: | 
| 885 if self.opts.make_HTML: | 887 while True: | 
| 886 self.makeHtml() | 888 err += tmp_stderr.read( buffsize ) | 
| 887 return retval | 889 if not err or len( stderr ) % buffsize != 0: | 
| 888 | 890 break | 
| 889 def runBash(self): | 891 except OverflowError: | 
| 890 """ | 892 pass | 
| 891 cannot use - for bash so use self.sfile | 893 tmp_stderr.close() | 
| 892 """ | 894 else: | 
| 895 p = subprocess.Popen(self.cl,shell=False) | |
| 896 retval = p.wait() | |
| 893 if self.opts.output_dir: | 897 if self.opts.output_dir: | 
| 894 s = '## Toolfactory generated command line = %s\n' % ' '.join(self.cl) | 898 if retval <> 0 and err: # problem | 
| 895 sto = open(self.tlog,'w') | 899 print >> sys.stderr,err | 
| 896 sto.write(s) | |
| 897 sto.flush() | |
| 898 p = subprocess.Popen(self.cl,shell=False,stdout=sto,stderr=sto,cwd=self.opts.output_dir) | |
| 899 else: | |
| 900 p = subprocess.Popen(self.cl,shell=False) | |
| 901 retval = p.wait() | |
| 902 if self.opts.output_dir: | |
| 903 sto.close() | |
| 904 if self.opts.make_HTML: | 900 if self.opts.make_HTML: | 
| 905 self.makeHtml() | 901 self.makeHtml() | 
| 906 return retval | 902 return retval | 
| 903 | |
| 907 | 904 | 
| 908 | 905 | 
| 909 def main(): | 906 def main(): | 
| 910 u = """ | 907 u = """ | 
| 911 This is a Galaxy wrapper. It expects to be called by a special purpose tool.xml as: | 908 This is a Galaxy wrapper. It expects to be called by a special purpose tool.xml as: | 
