Mercurial > repos > fubar > tool_factory_2
comparison toolfactory/rgToolFactory2.py @ 30:6f48315c32c1 draft
Uploaded
author | fubar |
---|---|
date | Fri, 07 Aug 2020 07:54:23 -0400 |
parents | |
children | 4d578c8c1613 |
comparison
equal
deleted
inserted
replaced
29:6db39cbc3242 | 30:6f48315c32c1 |
---|---|
1 # rgToolFactory.py | |
2 # see https://github.com/fubar2/toolfactory | |
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://github.com/fubar2/toolfactory | |
9 # | |
10 # July 2020: BCC was fun and I feel like rip van winkle after 5 years. | |
11 # Decided to | |
12 # 1. Fix the toolfactory so it works - done for simplest case | |
13 # 2. Fix planemo so the toolfactory function works | |
14 # 3. Rewrite bits using galaxyxml functions where that makes sense - done | |
15 # | |
16 # removed all the old complications including making the new tool use this same script | |
17 # galaxyxml now generates the tool xml https://github.com/hexylena/galaxyxml | |
18 # No support for automatic HTML file creation from arbitrary outputs | |
19 # TODO: add option to run that code as a post execution hook | |
20 # TODO: add additional history input parameters - currently only one | |
21 | |
22 import sys | |
23 import subprocess | |
24 import shutil | |
25 import os | |
26 import time | |
27 import tempfile | |
28 import argparse | |
29 import tarfile | |
30 import re | |
31 import galaxyxml.tool as gxt | |
32 import galaxyxml.tool.parameters as gxtp | |
33 import logging | |
34 | |
35 | |
36 progname = os.path.split(sys.argv[0])[1] | |
37 myversion = 'V2.1 July 2020' | |
38 verbose = True | |
39 debug = True | |
40 toolFactoryURL = 'https://github.com/fubar2/toolfactory' | |
41 ourdelim = '~~~' | |
42 | |
43 # --input_files="$input_files~~~$CL~~~$input_formats~~~$input_label~~~$input_help" | |
44 IPATHPOS = 0 | |
45 ICLPOS = 1 | |
46 IFMTPOS = 2 | |
47 ILABPOS = 3 | |
48 IHELPOS = 4 | |
49 IOCLPOS = 5 | |
50 # --output_files "$otab.history_name~~~$otab.history_format~~~$otab.CL | |
51 ONAMEPOS = 0 | |
52 OFMTPOS = 1 | |
53 OCLPOS = 2 | |
54 OOCLPOS = 3 | |
55 | |
56 #--additional_parameters="$i.param_name~~~$i.param_value~~~$i.param_label~~~$i.param_help~~~$i.param_type~~~$i.CL" | |
57 ANAMEPOS = 0 | |
58 AVALPOS = 1 | |
59 ALABPOS = 2 | |
60 AHELPPOS = 3 | |
61 ATYPEPOS = 4 | |
62 ACLPOS = 5 | |
63 AOCLPOS = 6 | |
64 | |
65 def timenow(): | |
66 """return current time as a string | |
67 """ | |
68 return time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(time.time())) | |
69 | |
70 | |
71 def quote_non_numeric(s): | |
72 """return a prequoted string for non-numerics | |
73 useful for perl and Rscript parameter passing? | |
74 """ | |
75 try: | |
76 _ = float(s) | |
77 return s | |
78 except ValueError: | |
79 return '"%s"' % s | |
80 | |
81 | |
82 html_escape_table = { | |
83 "&": "&", | |
84 ">": ">", | |
85 "<": "<", | |
86 "$": r"\$" | |
87 } | |
88 | |
89 | |
90 def html_escape(text): | |
91 """Produce entities within text.""" | |
92 return "".join(html_escape_table.get(c, c) for c in text) | |
93 | |
94 | |
95 def html_unescape(text): | |
96 """Revert entities within text. Multiple character targets so use replace""" | |
97 t = text.replace('&', '&') | |
98 t = t.replace('>', '>') | |
99 t = t.replace('<', '<') | |
100 t = t.replace('\\$', '$') | |
101 return t | |
102 | |
103 | |
104 def parse_citations(citations_text): | |
105 """ | |
106 """ | |
107 citations = [c for c in citations_text.split("**ENTRY**") if c.strip()] | |
108 citation_tuples = [] | |
109 for citation in citations: | |
110 if citation.startswith("doi"): | |
111 citation_tuples.append(("doi", citation[len("doi"):].strip())) | |
112 else: | |
113 citation_tuples.append( | |
114 ("bibtex", citation[len("bibtex"):].strip())) | |
115 return citation_tuples | |
116 | |
117 | |
118 class ScriptRunner: | |
119 """Wrapper for an arbitrary script | |
120 uses galaxyxml | |
121 | |
122 """ | |
123 | |
124 | |
125 def __init__(self, args=None): | |
126 """ | |
127 prepare command line cl for running the tool here | |
128 and prepare elements needed for galaxyxml tool generation | |
129 """ | |
130 | |
131 self.infiles = [x.split(ourdelim) for x in args.input_files] | |
132 self.outfiles = [x.split(ourdelim) for x in args.output_files] | |
133 self.addpar = [x.split(ourdelim) for x in args.additional_parameters] | |
134 self.args = args | |
135 self.cleanuppar() | |
136 self.lastclredirect = None | |
137 self.lastxclredirect = None | |
138 self.cl = [] | |
139 self.xmlcl = [] | |
140 aCL = self.cl.append | |
141 assert args.parampass in ['0','argparse','positional'],'Parameter passing in args.parampass must be "0","positional" or "argparse"' | |
142 self.tool_name = re.sub('[^a-zA-Z0-9_]+', '', args.tool_name) | |
143 self.tool_id = self.tool_name | |
144 self.xmlfile = '%s.xml' % self.tool_name | |
145 if self.args.runmode == "Executable" or self.args.runmode == "system": # binary - no need | |
146 aCL(self.args.exe_package) # this little CL will just run | |
147 else: | |
148 rx = open(self.args.script_path, 'r').readlines() | |
149 rx = [x.rstrip() for x in rx ] | |
150 rxcheck = [x.strip() for x in rx if x.strip() > ''] | |
151 assert len(rxcheck) > 0,"Supplied script is empty. Cannot run" | |
152 self.script = '\n'.join(rx) | |
153 fhandle, self.sfile = tempfile.mkstemp( | |
154 prefix=self.tool_name, suffix=".%s" % (args.interpreter_name)) | |
155 tscript = open(self.sfile, 'w') | |
156 tscript.write(self.script) | |
157 tscript.close() | |
158 self.indentedScript = " %s" % '\n'.join( | |
159 [' %s' % html_escape(x) for x in rx]) | |
160 self.escapedScript = "%s" % '\n'.join( | |
161 [' %s' % html_escape(x) for x in rx]) | |
162 art = '%s.%s' % (self.tool_name, args.interpreter_name) | |
163 artifact = open(art, 'wb') | |
164 artifact.write(bytes(self.script, "utf8")) | |
165 artifact.close() | |
166 aCL(self.args.interpreter_name) | |
167 aCL(self.sfile) | |
168 self.elog = "%s_error_log.txt" % self.tool_name | |
169 self.tlog = "%s_runner_log.txt" % self.tool_name | |
170 | |
171 if self.args.parampass == '0': | |
172 self.clsimple() | |
173 else: | |
174 clsuffix = [] | |
175 xclsuffix = [] | |
176 for i, p in enumerate(self.infiles): | |
177 appendme = [p[IOCLPOS], p[ICLPOS], p[IPATHPOS]] | |
178 clsuffix.append(appendme) | |
179 xclsuffix.append([p[IOCLPOS],p[ICLPOS],'$%s' % p[ICLPOS]]) | |
180 #print('##infile i=%d, appendme=%s' % (i,appendme)) | |
181 for i, p in enumerate(self.outfiles): | |
182 if p[OOCLPOS] == "STDOUT": | |
183 self.lastclredirect = ['>',p[ONAMEPOS]] | |
184 self.lastxclredirect = ['>','$%s' % p[OCLPOS]] | |
185 #print('##outfiles i=%d lastclredirect = %s' % (i,self.lastclredirect)) | |
186 else: | |
187 appendme = [p[OOCLPOS], p[OCLPOS],p[ONAMEPOS]] | |
188 clsuffix.append(appendme) | |
189 xclsuffix.append([p[OOCLPOS], p[OCLPOS],'$%s' % p[ONAMEPOS]]) | |
190 #print('##outfiles i=%d' % i,'appendme',appendme) | |
191 for p in self.addpar: | |
192 appendme = [p[AOCLPOS], p[ACLPOS], p[AVALPOS]] | |
193 clsuffix.append(appendme) | |
194 xclsuffix.append([p[AOCLPOS], p[ACLPOS], '"$%s"' % p[ANAMEPOS]]) | |
195 #print('##adpar %d' % i,'appendme=',appendme) | |
196 clsuffix.sort() | |
197 xclsuffix.sort() | |
198 self.xclsuffix = xclsuffix | |
199 self.clsuffix = clsuffix | |
200 if self.args.parampass == 'positional': | |
201 self.clpositional() | |
202 else: | |
203 self.clargparse() | |
204 | |
205 def cleanuppar(self): | |
206 """ positional parameters are complicated by their numeric ordinal""" | |
207 for i,p in enumerate(self.infiles): | |
208 if self.args.parampass == 'positional': | |
209 assert p[ICLPOS].isdigit(), "Positional parameters must be ordinal integers - got %s for %s" % (p[ICLPOS],p[ILABPOS]) | |
210 p.append(p[ICLPOS]) | |
211 if p[ICLPOS].isdigit() or self.args.parampass == "0": | |
212 scl = 'input%d' % (i+1) | |
213 p[ICLPOS] = scl | |
214 self.infiles[i] = p | |
215 for i,p in enumerate(self.outfiles): # trying to automagically gather using extensions | |
216 if self.args.parampass == 'positional' and p[OCLPOS] != "STDOUT": | |
217 assert p[OCLPOS].isdigit(), "Positional parameters must be ordinal integers - got %s for %s" % (p[OCLPOS],p[ONAMEPOS]) | |
218 p.append(p[OCLPOS]) | |
219 if p[OCLPOS].isdigit() or p[OCLPOS] == "STDOUT": | |
220 scl = p[ONAMEPOS] | |
221 p[OCLPOS] = scl | |
222 self.outfiles[i] = p | |
223 for i,p in enumerate(self.addpar): | |
224 if self.args.parampass == 'positional': | |
225 assert p[ACLPOS].isdigit(), "Positional parameters must be ordinal integers - got %s for %s" % (p[ACLPOS],p[ANAMEPOS]) | |
226 p.append(p[ACLPOS]) | |
227 if p[ACLPOS].isdigit(): | |
228 scl = 'input%s' % p[ACLPOS] | |
229 p[ACLPOS] = scl | |
230 self.addpar[i] = p | |
231 | |
232 | |
233 | |
234 def clsimple(self): | |
235 """ no parameters - uses < and > for i/o | |
236 """ | |
237 aCL = self.cl.append | |
238 aCL('<') | |
239 aCL(self.infiles[0][IPATHPOS]) | |
240 aCL('>') | |
241 aCL(self.outfiles[0][OCLPOS]) | |
242 aXCL = self.xmlcl.append | |
243 aXCL('<') | |
244 aXCL('$%s' % self.infiles[0][ICLPOS]) | |
245 aXCL('>') | |
246 aXCL('$%s' % self.outfiles[0][ONAMEPOS]) | |
247 | |
248 | |
249 def clpositional(self): | |
250 # inputs in order then params | |
251 aCL = self.cl.append | |
252 for (o_v,k, v) in self.clsuffix: | |
253 if " " in v: | |
254 aCL("%s" % v) | |
255 else: | |
256 aCL(v) | |
257 aXCL = self.xmlcl.append | |
258 for (o_v,k, v) in self.xclsuffix: | |
259 aXCL(v) | |
260 if self.lastxclredirect: | |
261 aXCL(self.lastxclredirect[0]) | |
262 aXCL(self.lastxclredirect[1]) | |
263 | |
264 | |
265 | |
266 def clargparse(self): | |
267 """ argparse style | |
268 """ | |
269 aCL = self.cl.append | |
270 aXCL = self.xmlcl.append | |
271 # inputs then params in argparse named form | |
272 for (o_v,k, v) in self.xclsuffix: | |
273 aXCL(k) | |
274 aXCL(v) | |
275 for (o_v,k, v) in self.clsuffix: | |
276 if len(k.strip()) == 1: | |
277 k = '-%s' % k | |
278 else: | |
279 k = '--%s' % k | |
280 aCL(k) | |
281 aCL(v) | |
282 | |
283 | |
284 | |
285 | |
286 def makeXML(self): | |
287 """ | |
288 Create a Galaxy xml tool wrapper for the new script | |
289 Uses galaxyhtml | |
290 Hmmm. How to get the command line into correct order... | |
291 """ | |
292 | |
293 if self.args.interpreter_name: | |
294 exe = "$runMe" | |
295 interp = self.args.interpreter_name | |
296 else: | |
297 interp = None | |
298 exe = self.args.exe_package | |
299 assert exe is not None, 'No interpeter or executable passed in to makeXML' | |
300 tool = gxt.Tool(self.args.tool_name, self.tool_id, | |
301 self.args.tool_version, self.args.tool_desc, exe) | |
302 tool.command_line_override = self.xmlcl | |
303 print('#### tool cl override=',self.xmlcl) | |
304 if interp: | |
305 tool.interpreter = interp | |
306 if self.args.help_text: | |
307 helptext = open(self.args.help_text, 'r').readlines() | |
308 helptext = [html_escape(x) for x in helptext] | |
309 tool.help = ''.join([x for x in helptext]) | |
310 else: | |
311 tool.help = 'Please ask the tool author (%s) for help \ | |
312 as none was supplied at tool generation\n' % (self.args.user_email) | |
313 tool.version_command = None # do not want | |
314 tinputs = gxtp.Inputs() | |
315 toutputs = gxtp.Outputs() | |
316 requirements = gxtp.Requirements() | |
317 testparam = [] | |
318 is_positional = (self.args.parampass == 'positional') | |
319 if self.args.interpreter_name: | |
320 if self.args.interpreter_name == 'python': | |
321 requirements.append(gxtp.Requirement( | |
322 'package', 'python', self.args.interpreter_version)) | |
323 elif self.args.interpreter_name not in ['bash', 'sh']: | |
324 requirements.append(gxtp.Requirement( | |
325 'package', self.args.interpreter_name, self.args.interpreter_version)) | |
326 else: | |
327 if self.args.exe_package and self.args.parampass != "system": | |
328 requirements.append(gxtp.Requirement( | |
329 'package', self.args.exe_package, self.args.exe_package_version)) | |
330 tool.requirements = requirements | |
331 if self.args.parampass == '0': | |
332 alab = self.infiles[0][ILABPOS] | |
333 if len(alab) == 0: | |
334 alab = self.infiles[0][ICLPOS] | |
335 max1s = 'Maximum one input if parampass is 0 - more than one input files supplied - %s' % str(self.infiles) | |
336 assert len(self.infiles) == 1,max1s | |
337 newname = self.infiles[0][ICLPOS] | |
338 aninput = gxtp.DataParam(newname, optional=False, label=alab, help=self.infiles[0][IHELPOS], | |
339 format=self.infiles[0][IFMTPOS], multiple=False, num_dashes=0) | |
340 aninput.command_line_override = '< $%s' % newname | |
341 aninput.positional = is_positional | |
342 tinputs.append(aninput) | |
343 tp = gxtp.TestParam(name=newname, value='%s_sample' % newname) | |
344 testparam.append(tp) | |
345 newname = self.outfiles[0][OCLPOS] | |
346 newfmt = self.outfiles[0][OFMTPOS] | |
347 anout = gxtp.OutputData(newname, format=newfmt, num_dashes=0) | |
348 anout.command_line_override = '> $%s' % newname | |
349 anout.positional = is_positional | |
350 toutputs.append(anout) | |
351 tp = gxtp.TestOutput(name=newname, value='%s_sample' % newname,format=newfmt) | |
352 testparam.append(tp) | |
353 else: | |
354 for p in self.outfiles: | |
355 newname,newfmt,newcl,oldcl = p | |
356 if is_positional: | |
357 ndash = 0 | |
358 else: | |
359 ndash = 2 | |
360 if len(newcl) < 2: | |
361 ndash = 1 | |
362 aparm = gxtp.OutputData(newcl, format=newfmt, num_dashes=ndash) | |
363 aparm.positional = is_positional | |
364 if is_positional: | |
365 if oldcl == "STDOUT": | |
366 aparm.positional = 9999999 | |
367 aparm.command_line_override = "> $%s" % newcl | |
368 else: | |
369 aparm.positional = int(oldcl) | |
370 aparm.command_line_override = '$%s' % newcl | |
371 toutputs.append(aparm) | |
372 tp = gxtp.TestOutput(name=newcl, value='%s_sample' % newcl ,format=newfmt) | |
373 testparam.append(tp) | |
374 for p in self.infiles: | |
375 newname = p[ICLPOS] | |
376 newfmt = p[IFMTPOS] | |
377 if is_positional: | |
378 ndash = 0 | |
379 else: | |
380 if len(newname) > 1: | |
381 ndash = 2 | |
382 else: | |
383 ndash = 1 | |
384 if not len(p[ILABPOS]) > 0: | |
385 alab = p[ICLPOS] | |
386 else: | |
387 alab = p[ILABPOS] | |
388 aninput = gxtp.DataParam(newname, optional=False, label=alab, help=p[IHELPOS], | |
389 format=newfmt, multiple=False, num_dashes=ndash) | |
390 aninput.positional = is_positional | |
391 if is_positional: | |
392 aninput.positional = is_positional | |
393 tinputs.append(aninput) | |
394 tparm = gxtp.TestParam(name=newname, value='%s_sample' % newname ) | |
395 testparam.append(tparm) | |
396 for p in self.addpar: | |
397 newname, newval, newlabel, newhelp, newtype, newcl, oldcl = p | |
398 if not len(newlabel) > 0: | |
399 newlabel = newname | |
400 if is_positional: | |
401 ndash = 0 | |
402 else: | |
403 if len(newname) > 1: | |
404 ndash = 2 | |
405 else: | |
406 ndash = 1 | |
407 if newtype == "text": | |
408 aparm = gxtp.TextParam( | |
409 newname, label=newlabel, help=newhelp, value=newval, num_dashes=ndash) | |
410 elif newtype == "integer": | |
411 aparm = gxtp.IntegerParam( | |
412 newname, label=newname, help=newhelp, value=newval, num_dashes=ndash) | |
413 elif newtype == "float": | |
414 aparm = gxtp.FloatParam( | |
415 newname, label=newname, help=newhelp, value=newval, num_dashes=ndash) | |
416 else: | |
417 raise ValueError('Unrecognised parameter type "%s" for\ | |
418 additional parameter %s in makeXML' % (newtype, newname)) | |
419 aparm.positional = is_positional | |
420 if is_positional: | |
421 aninput.positional = int(oldcl) | |
422 tinputs.append(aparm) | |
423 tparm = gxtp.TestParam(newname, value=newval) | |
424 testparam.append(tparm) | |
425 tool.outputs = toutputs | |
426 tool.inputs = tinputs | |
427 if not self.args.runmode in ['Executable','system']: | |
428 configfiles = gxtp.Configfiles() | |
429 configfiles.append(gxtp.Configfile(name="runMe", text=self.script)) | |
430 tool.configfiles = configfiles | |
431 tests = gxtp.Tests() | |
432 test_a = gxtp.Test() | |
433 for tp in testparam: | |
434 test_a.append(tp) | |
435 tests.append(test_a) | |
436 tool.tests = tests | |
437 tool.add_comment('Created by %s at %s using the Galaxy Tool Factory.' % ( | |
438 self.args.user_email, timenow())) | |
439 tool.add_comment('Source in git at: %s' % (toolFactoryURL)) | |
440 tool.add_comment( | |
441 'Cite: Creating re-usable tools from scripts doi: 10.1093/bioinformatics/bts573') | |
442 exml = tool.export() | |
443 xf = open(self.xmlfile, 'w') | |
444 xf.write(exml) | |
445 xf.write('\n') | |
446 xf.close() | |
447 # ready for the tarball | |
448 | |
449 def makeTooltar(self): | |
450 """ | |
451 a tool is a gz tarball with eg | |
452 /toolname/tool.xml /toolname/tool.py /toolname/test-data/test1_in.foo ... | |
453 NOTE names for test inputs and outputs are munged here so must | |
454 correspond to actual input and output names used on the generated cl | |
455 """ | |
456 retval = self.run() | |
457 if retval: | |
458 sys.stderr.write( | |
459 '## Run failed. Cannot build yet. Please fix and retry') | |
460 sys.exit(1) | |
461 tdir = 'tfout' | |
462 if not os.path.exists(tdir): | |
463 os.mkdir(tdir) | |
464 self.makeXML() | |
465 testdir = os.path.join(tdir,'test-data') | |
466 if not os.path.exists(testdir): | |
467 os.mkdir(testdir) # make tests directory | |
468 for p in self.infiles: | |
469 pth = p[IPATHPOS] | |
470 dest = os.path.join(testdir, '%s_sample' % | |
471 p[ICLPOS]) | |
472 shutil.copyfile(pth, dest) | |
473 for p in self.outfiles: | |
474 pth = p[OCLPOS] | |
475 if p[OOCLPOS] == 'STDOUT' or self.args.parampass == "0": | |
476 pth = p[ONAMEPOS] | |
477 dest = os.path.join(testdir,'%s_sample' % p[ONAMEPOS]) | |
478 shutil.copyfile(pth, dest) | |
479 dest = os.path.join(tdir, p[ONAMEPOS]) | |
480 shutil.copyfile(pth, dest) | |
481 else: | |
482 pth = p[OCLPOS] | |
483 dest = os.path.join(testdir,'%s_sample' % p[OCLPOS]) | |
484 shutil.copyfile(pth, dest) | |
485 dest = os.path.join(tdir, p[OCLPOS]) | |
486 shutil.copyfile(pth, dest) | |
487 | |
488 if os.path.exists(self.tlog) and os.stat(self.tlog).st_size > 0: | |
489 shutil.copyfile(self.tlog, os.path.join( | |
490 testdir, 'test1_log.txt')) | |
491 if not self.args.runmode in ['Executable','system']: | |
492 stname = os.path.join(tdir, '%s' % (self.sfile)) | |
493 if not os.path.exists(stname): | |
494 shutil.copyfile(self.sfile, stname) | |
495 xtname = os.path.join(tdir,self.xmlfile) | |
496 if not os.path.exists(xtname): | |
497 shutil.copyfile(self.xmlfile, xtname) | |
498 tarpath = 'toolfactory_%s.tgz' % self.tool_name | |
499 tf = tarfile.open(tarpath,"w:gz") | |
500 tf.add(name=tdir,arcname=self.tool_name) | |
501 tf.close() | |
502 shutil.copyfile(tarpath, self.args.new_tool) | |
503 return retval | |
504 | |
505 def run(self): | |
506 """ | |
507 Some devteam tools have this defensive stderr read so I'm keeping with the faith | |
508 Feel free to update. | |
509 """ | |
510 s = 'run cl=%s' % str(self.cl) | |
511 | |
512 logging.debug(s) | |
513 scl = ' '.join(self.cl) | |
514 err = None | |
515 if self.args.parampass != '0': | |
516 ste = open(self.elog, 'wb') | |
517 if self.lastclredirect: | |
518 sto = open(self.lastclredirect[1],'wb') # is name of an output file | |
519 else: | |
520 sto = open(self.tlog, 'wb') | |
521 sto.write( | |
522 bytes('## Executing Toolfactory generated command line = %s\n' % scl, "utf8")) | |
523 sto.flush() | |
524 p = subprocess.run(self.cl, shell=False, stdout=sto, | |
525 stderr=ste) | |
526 sto.close() | |
527 ste.close() | |
528 tmp_stderr = open(self.elog, 'rb') | |
529 err = '' | |
530 buffsize = 1048576 | |
531 try: | |
532 while True: | |
533 err += str(tmp_stderr.read(buffsize)) | |
534 if not err or len(err) % buffsize != 0: | |
535 break | |
536 except OverflowError: | |
537 pass | |
538 tmp_stderr.close() | |
539 retval = p.returncode | |
540 else: # work around special case of simple scripts that take stdin and write to stdout | |
541 sti = open(self.infiles[0][IPATHPOS], 'rb') | |
542 sto = open(self.outfiles[0][ONAMEPOS], 'wb') | |
543 # must use shell to redirect | |
544 p = subprocess.run(self.cl, shell=False, stdout=sto, stdin=sti) | |
545 retval = p.returncode | |
546 sto.close() | |
547 sti.close() | |
548 if os.path.isfile(self.tlog) and os.stat(self.tlog).st_size == 0: | |
549 os.unlink(self.tlog) | |
550 if os.path.isfile(self.elog) and os.stat(self.elog).st_size == 0: | |
551 os.unlink(self.elog) | |
552 if p.returncode != 0 and err: # problem | |
553 sys.stderr.write(err) | |
554 logging.debug('run done') | |
555 return retval | |
556 | |
557 | |
558 def main(): | |
559 """ | |
560 This is a Galaxy wrapper. It expects to be called by a special purpose tool.xml as: | |
561 <command interpreter="python">rgBaseScriptWrapper.py --script_path "$scriptPath" --tool_name "foo" --interpreter "Rscript" | |
562 </command> | |
563 """ | |
564 parser = argparse.ArgumentParser() | |
565 a = parser.add_argument | |
566 a('--script_path', default='') | |
567 a('--tool_name', default=None) | |
568 a('--interpreter_name', default=None) | |
569 a('--interpreter_version', default=None) | |
570 a('--exe_package', default=None) | |
571 a('--exe_package_version', default=None) | |
572 a('--input_files', default=[], action="append") | |
573 a('--output_files', default=[], action="append") | |
574 a('--user_email', default='Unknown') | |
575 a('--bad_user', default=None) | |
576 a('--make_Tool', default=None) | |
577 a('--help_text', default=None) | |
578 a('--tool_desc', default=None) | |
579 a('--tool_version', default=None) | |
580 a('--citations', default=None) | |
581 a('--additional_parameters', action='append', default=[]) | |
582 a('--edit_additional_parameters', action="store_true", default=False) | |
583 a('--parampass', default="positional") | |
584 a('--tfout', default="./tfout") | |
585 a('--new_tool',default="new_tool") | |
586 a('--runmode',default=None) | |
587 args = parser.parse_args() | |
588 assert not args.bad_user, 'UNAUTHORISED: %s is NOT authorized to use this tool until Galaxy admin adds %s to "admin_users" in the Galaxy configuration file' % ( | |
589 args.bad_user, args.bad_user) | |
590 assert args.tool_name, '## Tool Factory expects a tool name - eg --tool_name=DESeq' | |
591 assert (args.interpreter_name or args.exe_package), '## Tool Factory wrapper expects an interpreter - eg --interpreter_name=Rscript or an executable package findable by the dependency management package' | |
592 assert args.exe_package or (len(args.script_path) > 0 and os.path.isfile( | |
593 args.script_path)), '## Tool Factory wrapper expects a script path - eg --script_path=foo.R if no executable' | |
594 args.input_files = [x.replace('"', '').replace("'", '') | |
595 for x in args.input_files] | |
596 # remove quotes we need to deal with spaces in CL params | |
597 for i, x in enumerate(args.additional_parameters): | |
598 args.additional_parameters[i] = args.additional_parameters[i].replace( | |
599 '"', '') | |
600 r = ScriptRunner(args) | |
601 if args.make_Tool: | |
602 retcode = r.makeTooltar() | |
603 else: | |
604 retcode = r.run() | |
605 if retcode: | |
606 sys.exit(retcode) # indicate failure to job runner | |
607 | |
608 | |
609 if __name__ == "__main__": | |
610 main() |