comparison toolfactory/rgToolFactory2.py @ 48:5a7a5b06bce0 draft

Uploaded
author fubar
date Sun, 23 Aug 2020 21:03:48 -0400
parents
children 35a912ce0c83
comparison
equal deleted inserted replaced
47:e95d9fe5ab50 48:5a7a5b06bce0
1 #!/usr/bin/env python
2 # rgToolFactory.py
3 # see https://github.com/fubar2/toolfactory
4 #
5 # copyright ross lazarus (ross stop lazarus at gmail stop com) May 2012
6 #
7 # all rights reserved
8 # Licensed under the LGPL
9 # suggestions for improvement and bug fixes welcome at https://github.com/fubar2/toolfactory
10 #
11 # July 2020: BCC was fun and I feel like rip van winkle after 5 years.
12 # Decided to
13 # 1. Fix the toolfactory so it works - done for simplest case
14 # 2. Fix planemo so the toolfactory function works
15 # 3. Rewrite bits using galaxyxml functions where that makes sense - done
16 #
17 # removed all the old complications including making the new tool use this same script
18 # galaxyxml now generates the tool xml https://github.com/hexylena/galaxyxml
19 # No support for automatic HTML file creation from arbitrary outputs
20 # essential problem is to create two command lines - one for the tool xml and a different
21 # one to run the executable with the supplied test data and settings
22 # Be simpler to write the tool, then run it with planemo and soak up the test outputs.
23
24
25
26 import argparse
27 import logging
28 import os
29 import re
30 import shutil
31 import subprocess
32 import sys
33 import tarfile
34 import tempfile
35 import time
36
37 import galaxyxml.tool as gxt
38 import galaxyxml.tool.parameters as gxtp
39
40 import lxml
41
42 import yaml
43
44 myversion = "V2.1 July 2020"
45 verbose = True
46 debug = True
47 toolFactoryURL = "https://github.com/fubar2/toolfactory"
48 ourdelim = "~~~"
49
50 # --input_files="$input_files~~~$CL~~~$input_formats~~~$input_label
51 # ~~~$input_help"
52 IPATHPOS = 0
53 ICLPOS = 1
54 IFMTPOS = 2
55 ILABPOS = 3
56 IHELPOS = 4
57 IOCLPOS = 5
58
59 # --output_files "$otab.history_name~~~$otab.history_format~~~$otab.CL
60 ONAMEPOS = 0
61 OFMTPOS = 1
62 OCLPOS = 2
63 OOCLPOS = 3
64
65 # --additional_parameters="$i.param_name~~~$i.param_value~~~
66 # $i.param_label~~~$i.param_help~~~$i.param_type~~~$i.CL~~~i$.param_CLoverride"
67 ANAMEPOS = 0
68 AVALPOS = 1
69 ALABPOS = 2
70 AHELPPOS = 3
71 ATYPEPOS = 4
72 ACLPOS = 5
73 AOVERPOS = 6
74 AOCLPOS = 7
75
76
77 foo = len(lxml.__version__)
78 # fug you, flake8. Say my name!
79
80 def timenow():
81 """return current time as a string
82 """
83 return time.strftime("%d/%m/%Y %H:%M:%S", time.localtime(time.time()))
84
85
86 def quote_non_numeric(s):
87 """return a prequoted string for non-numerics
88 useful for perl and Rscript parameter passing?
89 """
90 try:
91 _ = float(s)
92 return s
93 except ValueError:
94 return '"%s"' % s
95
96
97 html_escape_table = {"&": "&amp;", ">": "&gt;", "<": "&lt;", "$": r"\$"}
98
99
100 def html_escape(text):
101 """Produce entities within text."""
102 return "".join(html_escape_table.get(c, c) for c in text)
103
104
105 def html_unescape(text):
106 """Revert entities within text. Multiple character targets so use replace"""
107 t = text.replace("&amp;", "&")
108 t = t.replace("&gt;", ">")
109 t = t.replace("&lt;", "<")
110 t = t.replace("\\$", "$")
111 return t
112
113
114 def parse_citations(citations_text):
115 """
116 """
117 citations = [c for c in citations_text.split("**ENTRY**") if c.strip()]
118 citation_tuples = []
119 for citation in citations:
120 if citation.startswith("doi"):
121 citation_tuples.append(("doi", citation[len("doi") :].strip()))
122 else:
123 citation_tuples.append(
124 ("bibtex", citation[len("bibtex") :].strip())
125 )
126 return citation_tuples
127
128
129 class ScriptRunner:
130 """Wrapper for an arbitrary script
131 uses galaxyxml
132
133 """
134
135 def __init__(self, args=None):
136 """
137 prepare command line cl for running the tool here
138 and prepare elements needed for galaxyxml tool generation
139 """
140
141 self.infiles = [x.split(ourdelim) for x in args.input_files]
142 self.outfiles = [x.split(ourdelim) for x in args.output_files]
143 self.addpar = [x.split(ourdelim) for x in args.additional_parameters]
144 self.args = args
145 self.cleanuppar()
146 self.lastclredirect = None
147 self.lastxclredirect = None
148 self.cl = []
149 self.xmlcl = []
150 self.is_positional = self.args.parampass == "positional"
151 aCL = self.cl.append
152 assert args.parampass in [
153 "0",
154 "argparse",
155 "positional",
156 ], 'Parameter passing in args.parampass must be "0","positional" or "argparse"'
157 self.tool_name = re.sub("[^a-zA-Z0-9_]+", "", args.tool_name)
158 self.tool_id = self.tool_name
159 if self.args.interpreter_name:
160 exe = "$runMe"
161 else:
162 exe = self.args.exe_package
163 assert (
164 exe is not None
165 ), "No interpeter or executable passed in - nothing to run so cannot build"
166 self.tool = gxt.Tool(
167 self.args.tool_name,
168 self.tool_id,
169 self.args.tool_version,
170 self.args.tool_desc,
171 exe,
172 )
173 self.tooloutdir = "tfout"
174 self.repdir = "TF_run_report_tempdir"
175 self.testdir = os.path.join(self.tooloutdir, "test-data")
176 if not os.path.exists(self.tooloutdir):
177 os.mkdir(self.tooloutdir)
178 if not os.path.exists(self.testdir):
179 os.mkdir(self.testdir) # make tests directory
180 if not os.path.exists(self.repdir):
181 os.mkdir(self.repdir)
182 self.tinputs = gxtp.Inputs()
183 self.toutputs = gxtp.Outputs()
184 self.testparam = []
185 if (
186 self.args.runmode == "Executable" or self.args.runmode == "system"
187 ):
188 if len(self.args.cl_override) > 0:
189 for x in self.args.cl_override.split(' '):
190 aCL(x)
191 else:
192 aCL(self.args.exe_package) # this little CL will just run
193 else:
194 self.prepScript()
195 aCL(self.args.interpreter_name)
196 aCL(self.sfile)
197
198 self.elog = "%s_error_log.txt" % self.tool_name
199 self.tlog = "%s_runner_log.txt" % self.tool_name
200
201 if self.args.parampass == "0":
202 self.clsimple()
203 else:
204 clsuffix = []
205 xclsuffix = []
206 for i, p in enumerate(self.infiles):
207 if p[IOCLPOS] == "STDIN":
208 appendme = [
209 p[IOCLPOS],
210 p[ICLPOS],
211 p[IPATHPOS],
212 "< %s" % p[IPATHPOS],
213 ]
214 xappendme = [
215 p[IOCLPOS],
216 p[ICLPOS],
217 p[IPATHPOS],
218 "< $%s" % p[ICLPOS],
219 ]
220 else:
221 appendme = [p[IOCLPOS], p[ICLPOS], p[IPATHPOS], ""]
222 xappendme = [p[IOCLPOS], p[ICLPOS], "$%s" % p[ICLPOS], ""]
223 clsuffix.append(appendme)
224 xclsuffix.append(xappendme)
225 # print('##infile i=%d, appendme=%s' % (i,appendme))
226 for i, p in enumerate(self.outfiles):
227 if p[OOCLPOS] == "STDOUT":
228 self.lastclredirect = [">", p[ONAMEPOS]]
229 self.lastxclredirect = [">", "$%s" % p[OCLPOS]]
230 else:
231 clsuffix.append([p[OOCLPOS], p[OCLPOS], p[ONAMEPOS], ""])
232 xclsuffix.append(
233 [p[OOCLPOS], p[OCLPOS], "$%s" % p[ONAMEPOS], ""]
234 )
235 for p in self.addpar:
236 clsuffix.append(
237 [p[AOCLPOS], p[ACLPOS], p[AVALPOS], p[AOVERPOS]]
238 )
239 xclsuffix.append(
240 [p[AOCLPOS], p[ACLPOS], '"$%s"' % p[ANAMEPOS], p[AOVERPOS]]
241 )
242 clsuffix.sort()
243 xclsuffix.sort()
244 self.xclsuffix = xclsuffix
245 self.clsuffix = clsuffix
246 if self.args.parampass == "positional":
247 self.clpositional()
248 else:
249 self.clargparse()
250
251 def prepScript(self):
252 rx = open(self.args.script_path, "r").readlines()
253 rx = [x.rstrip() for x in rx]
254 rxcheck = [x.strip() for x in rx if x.strip() > ""]
255 assert len(rxcheck) > 0, "Supplied script is empty. Cannot run"
256 self.script = "\n".join(rx)
257 fhandle, self.sfile = tempfile.mkstemp(
258 prefix=self.tool_name, suffix="_%s" % (self.args.interpreter_name)
259 )
260 tscript = open(self.sfile, "w")
261 tscript.write(self.script)
262 tscript.close()
263 self.indentedScript = " %s" % "\n".join(
264 [" %s" % html_escape(x) for x in rx]
265 )
266 self.escapedScript = "%s" % "\n".join(
267 [" %s" % html_escape(x) for x in rx]
268 )
269 art = "%s.%s" % (self.tool_name, self.args.interpreter_name)
270 artifact = open(art, "wb")
271 if self.args.interpreter_name == "python":
272 artifact.write(bytes("#!/usr/bin/env python\n", "utf8"))
273 artifact.write(bytes(self.script, "utf8"))
274 artifact.close()
275
276 def cleanuppar(self):
277 """ positional parameters are complicated by their numeric ordinal"""
278 for i, p in enumerate(self.infiles):
279 if self.args.parampass == "positional":
280 assert p[ICLPOS].isdigit(), (
281 "Positional parameters must be ordinal integers - got %s for %s"
282 % (p[ICLPOS], p[ILABPOS])
283 )
284 p.append(p[ICLPOS])
285 if p[ICLPOS].isdigit() or self.args.parampass == "0":
286 scl = "input%d" % (i + 1)
287 p[ICLPOS] = scl
288 self.infiles[i] = p
289 for i, p in enumerate(
290 self.outfiles
291 ): # trying to automagically gather using extensions
292 if self.args.parampass == "positional" and p[OCLPOS] != "STDOUT":
293 assert p[OCLPOS].isdigit(), (
294 "Positional parameters must be ordinal integers - got %s for %s"
295 % (p[OCLPOS], p[ONAMEPOS])
296 )
297 p.append(p[OCLPOS])
298 if p[OCLPOS].isdigit() or p[OCLPOS] == "STDOUT":
299 scl = p[ONAMEPOS]
300 p[OCLPOS] = scl
301 self.outfiles[i] = p
302 for i, p in enumerate(self.addpar):
303 if self.args.parampass == "positional":
304 assert p[ACLPOS].isdigit(), (
305 "Positional parameters must be ordinal integers - got %s for %s"
306 % (p[ACLPOS], p[ANAMEPOS])
307 )
308 p.append(p[ACLPOS])
309 if p[ACLPOS].isdigit():
310 scl = "input%s" % p[ACLPOS]
311 p[ACLPOS] = scl
312 self.addpar[i] = p
313
314 def clsimple(self):
315 """ no parameters - uses < and > for i/o
316 """
317 aCL = self.cl.append
318 aCL("<")
319 aCL(self.infiles[0][IPATHPOS])
320 aCL(">")
321 aCL(self.outfiles[0][OCLPOS])
322 aXCL = self.xmlcl.append
323 aXCL("<")
324 aXCL("$%s" % self.infiles[0][ICLPOS])
325 aXCL(">")
326 aXCL("$%s" % self.outfiles[0][ONAMEPOS])
327
328 def clpositional(self):
329 # inputs in order then params
330 aCL = self.cl.append
331 for (o_v, k, v, koverride) in self.clsuffix:
332 if " " in v:
333 aCL("%s" % v)
334 else:
335 aCL(v)
336 aXCL = self.xmlcl.append
337 for (o_v, k, v, koverride) in self.xclsuffix:
338 aXCL(v)
339 if self.lastxclredirect:
340 aXCL(self.lastxclredirect[0])
341 aXCL(self.lastxclredirect[1])
342
343 def clargparse(self):
344 """ argparse style
345 """
346 aCL = self.cl.append
347 aXCL = self.xmlcl.append
348 # inputs then params in argparse named form
349 for (o_v, k, v, koverride) in self.xclsuffix:
350 if koverride > "":
351 k = koverride
352 elif len(k.strip()) == 1:
353 k = "-%s" % k
354 else:
355 k = "--%s" % k
356 aXCL(k)
357 aXCL(v)
358 for (o_v, k, v, koverride) in self.clsuffix:
359 if koverride > "":
360 k = koverride
361 elif len(k.strip()) == 1:
362 k = "-%s" % k
363 else:
364 k = "--%s" % k
365 aCL(k)
366 aCL(v)
367
368 def getNdash(self, newname):
369 if self.is_positional:
370 ndash = 0
371 else:
372 ndash = 2
373 if len(newname) < 2:
374 ndash = 1
375 return ndash
376
377 def doXMLparam(self):
378 """flake8 made me do this..."""
379 for p in self.outfiles:
380 newname, newfmt, newcl, oldcl = p
381 ndash = self.getNdash(newcl)
382 aparm = gxtp.OutputData(newcl, format=newfmt, num_dashes=ndash)
383 aparm.positional = self.is_positional
384 if self.is_positional:
385 if oldcl == "STDOUT":
386 aparm.positional = 9999999
387 aparm.command_line_override = "> $%s" % newcl
388 else:
389 aparm.positional = int(oldcl)
390 aparm.command_line_override = "$%s" % newcl
391 self.toutputs.append(aparm)
392 tp = gxtp.TestOutput(
393 name=newcl, value="%s_sample" % newcl, format=newfmt
394 )
395 self.testparam.append(tp)
396 for p in self.infiles:
397 newname = p[ICLPOS]
398 newfmt = p[IFMTPOS]
399 ndash = self.getNdash(newname)
400 if not len(p[ILABPOS]) > 0:
401 alab = p[ICLPOS]
402 else:
403 alab = p[ILABPOS]
404 aninput = gxtp.DataParam(
405 newname,
406 optional=False,
407 label=alab,
408 help=p[IHELPOS],
409 format=newfmt,
410 multiple=False,
411 num_dashes=ndash,
412 )
413 aninput.positional = self.is_positional
414 self.tinputs.append(aninput)
415 tparm = gxtp.TestParam(name=newname, value="%s_sample" % newname)
416 self.testparam.append(tparm)
417 for p in self.addpar:
418 newname, newval, newlabel, newhelp, newtype, newcl, override, oldcl = p
419 if not len(newlabel) > 0:
420 newlabel = newname
421 ndash = self.getNdash(newname)
422 if newtype == "text":
423 aparm = gxtp.TextParam(
424 newname,
425 label=newlabel,
426 help=newhelp,
427 value=newval,
428 num_dashes=ndash,
429 )
430 elif newtype == "integer":
431 aparm = gxtp.IntegerParam(
432 newname,
433 label=newname,
434 help=newhelp,
435 value=newval,
436 num_dashes=ndash,
437 )
438 elif newtype == "float":
439 aparm = gxtp.FloatParam(
440 newname,
441 label=newname,
442 help=newhelp,
443 value=newval,
444 num_dashes=ndash,
445 )
446 else:
447 raise ValueError(
448 'Unrecognised parameter type "%s" for\
449 additional parameter %s in makeXML'
450 % (newtype, newname)
451 )
452 aparm.positional = self.is_positional
453 if self.is_positional:
454 aninput.positional = int(oldcl)
455 self.tinputs.append(aparm)
456 self.tparm = gxtp.TestParam(newname, value=newval)
457 self.testparam.append(tparm)
458
459 def doNoXMLparam(self):
460 alab = self.infiles[0][ILABPOS]
461 if len(alab) == 0:
462 alab = self.infiles[0][ICLPOS]
463 max1s = (
464 "Maximum one input if parampass is 0 - more than one input files supplied - %s"
465 % str(self.infiles)
466 )
467 assert len(self.infiles) == 1, max1s
468 newname = self.infiles[0][ICLPOS]
469 aninput = gxtp.DataParam(
470 newname,
471 optional=False,
472 label=alab,
473 help=self.infiles[0][IHELPOS],
474 format=self.infiles[0][IFMTPOS],
475 multiple=False,
476 num_dashes=0,
477 )
478 aninput.command_line_override = "< $%s" % newname
479 aninput.positional = self.is_positional
480 self.tinputs.append(aninput)
481 tp = gxtp.TestParam(name=newname, value="%s_sample" % newname)
482 self.testparam.append(tp)
483 newname = self.outfiles[0][OCLPOS]
484 newfmt = self.outfiles[0][OFMTPOS]
485 anout = gxtp.OutputData(newname, format=newfmt, num_dashes=0)
486 anout.command_line_override = "> $%s" % newname
487 anout.positional = self.is_positional
488 self.toutputs.append(anout)
489 tp = gxtp.TestOutput(
490 name=newname, value="%s_sample" % newname, format=newfmt
491 )
492 self.testparam.append(tp)
493
494 def makeXML(self):
495 """
496 Create a Galaxy xml tool wrapper for the new script
497 Uses galaxyhtml
498 Hmmm. How to get the command line into correct order...
499 """
500 if self.args.cl_override:
501 self.tool.command_line_override = self.args.cl_override.split(' ') + self.xmlcl
502 else:
503 self.tool.command_line_override = self.xmlcl
504 if self.args.interpreter_name:
505 self.tool.interpreter = self.args.interpreter_name
506 if self.args.help_text:
507 helptext = open(self.args.help_text, "r").readlines()
508 helptext = [html_escape(x) for x in helptext]
509 self.tool.help = "".join([x for x in helptext])
510 else:
511 self.tool.help = (
512 "Please ask the tool author (%s) for help \
513 as none was supplied at tool generation\n"
514 % (self.args.user_email)
515 )
516 self.tool.version_command = None # do not want
517 requirements = gxtp.Requirements()
518
519 if self.args.interpreter_name:
520 if self.args.dependencies:
521 for d in self.args.dependencies.split(','):
522 requirements.append(
523 gxtp.Requirement(
524 "package", d, ""
525 )
526 )
527 if self.args.interpreter_name == "python":
528 requirements.append(
529 gxtp.Requirement(
530 "package", "python", self.args.interpreter_version
531 )
532 )
533 elif self.args.interpreter_name not in ["bash", "sh"]:
534 requirements.append(
535 gxtp.Requirement(
536 "package",
537 self.args.interpreter_name,
538 self.args.interpreter_version,
539 )
540 )
541 else:
542 if self.args.exe_package and self.args.parampass != "system":
543 requirements.append(
544 gxtp.Requirement(
545 "package",
546 self.args.exe_package,
547 self.args.exe_package_version,
548 )
549 )
550 self.tool.requirements = requirements
551 if self.args.parampass == "0":
552 self.doNoXMLparam()
553 else:
554 self.doXMLparam()
555 self.tool.outputs = self.toutputs
556 self.tool.inputs = self.tinputs
557 if self.args.runmode not in ["Executable", "system"]:
558 configfiles = gxtp.Configfiles()
559 configfiles.append(gxtp.Configfile(name="runMe", text=self.script))
560 self.tool.configfiles = configfiles
561 tests = gxtp.Tests()
562 test_a = gxtp.Test()
563 for tp in self.testparam:
564 test_a.append(tp)
565 tests.append(test_a)
566 self.tool.tests = tests
567 self.tool.add_comment(
568 "Created by %s at %s using the Galaxy Tool Factory."
569 % (self.args.user_email, timenow())
570 )
571 self.tool.add_comment("Source in git at: %s" % (toolFactoryURL))
572 self.tool.add_comment(
573 "Cite: Creating re-usable tools from scripts doi: \
574 10.1093/bioinformatics/bts573"
575 )
576 exml = self.tool.export()
577 xf = open('%s.xml' % self.tool_name, "w")
578 xf.write(exml)
579 xf.write("\n")
580 xf.close()
581 # ready for the tarball
582
583 def makeTooltar(self):
584 """
585 a tool is a gz tarball with eg
586 /toolname/tool.xml /toolname/tool.py /toolname/test-data/test1_in.foo ...
587 NOTE names for test inputs and outputs are munged here so must
588 correspond to actual input and output names used on the generated cl
589 """
590 retval = self.run()
591 if retval:
592 sys.stderr.write(
593 "## Run failed. Cannot build yet. Please fix and retry"
594 )
595 sys.exit(1)
596 self.makeXML()
597 for p in self.infiles:
598 pth = p[IPATHPOS]
599 dest = os.path.join(self.testdir, "%s_sample" % p[ICLPOS])
600 shutil.copyfile(pth, dest)
601 for p in self.outfiles:
602 pth = p[OCLPOS]
603 if p[OOCLPOS] == "STDOUT" or self.args.parampass == "0":
604 pth = p[ONAMEPOS]
605 dest = os.path.join(self.testdir, "%s_sample" % p[ONAMEPOS])
606 shutil.copyfile(pth, dest)
607 dest = os.path.join(self.tooloutdir, p[ONAMEPOS])
608 shutil.copyfile(pth, dest)
609 else:
610 pth = p[OCLPOS]
611 dest = os.path.join(self.testdir, "%s_sample" % p[OCLPOS])
612 shutil.copyfile(pth, dest)
613 dest = os.path.join(self.tooloutdir, p[OCLPOS])
614 shutil.copyfile(pth, dest)
615 if os.path.exists(self.tlog) and os.stat(self.tlog).st_size > 0:
616 shutil.copyfile(self.tlog, os.path.join(self.tooloutdir, "test1_log_outfiletxt"))
617 if self.args.runmode not in ["Executable", "system"]:
618 stname = os.path.join(self.tooloutdir, "%s" % (self.sfile))
619 if not os.path.exists(stname):
620 shutil.copyfile(self.sfile, stname)
621 xreal = '%s.xml' % self.tool_name
622 xout = os.path.join(self.tooloutdir,xreal)
623 shutil.copyfile(xreal, xout)
624 self.newtarpath = "toolfactory_%s.tgz" % self.tool_name
625 tf = tarfile.open(self.newtarpath, "w:gz")
626 tf.add(name=self.tooloutdir, arcname=self.tool_name)
627 tf.close()
628 shutil.copyfile(self.newtarpath, self.args.new_tool)
629 shutil.copyfile(xreal,"tool_xml.txt")
630 repoutnames = [x[OCLPOS] for x in self.outfiles]
631 with os.scandir('.') as outs:
632 for entry in outs:
633 if entry.name.endswith('.tgz') or not entry.is_file():
634 continue
635 if entry.name in repoutnames:
636 if '.' in entry.name:
637 ofne = os.path.splitext(entry.name)[1]
638 else:
639 ofne = '.txt'
640 ofn = '%s%s' % (entry.name.replace('.','_'),ofne)
641 shutil.copyfile(entry.name,os.path.join(self.repdir,ofn))
642 elif entry.name == "%s.xml" % self.tool_name:
643 shutil.copyfile(entry.name,os.path.join(self.repdir,"new_tool_xml.xml"))
644 return retval
645
646 def run(self):
647 """
648
649 """
650 s = "run cl=%s" % str(self.cl)
651
652 logging.debug(s)
653 scl = " ".join(self.cl)
654 err = None
655 if self.args.parampass != "0":
656 ste = open(self.elog, "wb")
657 if self.lastclredirect:
658 sto = open(
659 self.lastclredirect[1], "wb"
660 ) # is name of an output file
661 else:
662 sto = open(self.tlog, "wb")
663 sto.write(
664 bytes(
665 "## Executing Toolfactory generated command line = %s\n"
666 % scl,
667 "utf8",
668 )
669 )
670 sto.flush()
671 p = subprocess.run(self.cl, shell=False, stdout=sto, stderr=ste)
672 sto.close()
673 ste.close()
674 tmp_stderr = open(self.elog, "rb")
675 err = ""
676 buffsize = 1048576
677 try:
678 while True:
679 err += str(tmp_stderr.read(buffsize))
680 if not err or len(err) % buffsize != 0:
681 break
682 except OverflowError:
683 pass
684 tmp_stderr.close()
685 retval = p.returncode
686 else: # work around special case of simple scripts that take stdin and write to stdout
687 sti = open(self.infiles[0][IPATHPOS], "rb")
688 sto = open(self.outfiles[0][ONAMEPOS], "wb")
689 # must use shell to redirect
690 p = subprocess.run(self.cl, shell=False, stdout=sto, stdin=sti)
691 retval = p.returncode
692 sto.close()
693 sti.close()
694 if os.path.isfile(self.tlog) and os.stat(self.tlog).st_size == 0:
695 os.unlink(self.tlog)
696 if os.path.isfile(self.elog) and os.stat(self.elog).st_size == 0:
697 os.unlink(self.elog)
698 if retval != 0 and err: # problem
699 sys.stderr.write(err)
700 logging.debug("run done")
701 return retval
702
703 def install_load(self):
704 testres = self.planemo_test()
705 if testres == 0:
706 self.planemo_shedload()
707 self,eph_galaxy_load()
708 else:
709 stderr.write('Planemo test failed - tool %s was not installed' % self.args.tool_name)
710
711
712 def planemo_shedload(self):
713 """
714 planemo shed_create --shed_target testtoolshed
715 planemo shed_update --check_diff --shed_target testtoolshed
716 """
717 cll = ['planemo', 'shed_create', '--shed_target','local']
718 p = subprocess.run(cll, shell=False,cwd=self.tooloutdir )
719 if p.returncode != 0:
720 print('Repository %s exists' % self.args.tool_name)
721 else:
722 print('initiated %s' % self.args.tool_name)
723 cll = ['planemo', 'shed_upload', '--shed_target','local','-r','--owner','fubar',
724 '--name',self.args.tool_name,'--shed_key',self.args.toolshed_api_key,'--tar',self.newtarpath]
725 p = subprocess.run(cll, shell=False,cwd=self.tooloutdir)
726 print('Ran',' '.join(cll),'got',p.returncode)
727 return p.returncode
728
729
730 def planemo_test(self):
731 """planemo is a requirement so is available
732 """
733 cll = ['planemo', 'test', '--galaxy_root', self.args.galaxy_root,
734 self.args.tool_name]
735 p = subprocess.run(cll, shell=False)
736 ols = os.listdir('.')
737 for fn in ols:
738 if '.' in fn:
739 ofne = os.path.splitext(fn)[1]
740 else:
741 ofne = '.txt'
742 ofn = '%s%s' % (fn.replace('.','_'),ofne)
743 shutil.copyfile(fn,os.path.join(self.repdir,ofn))
744 return p.returncode
745
746
747 def eph_galaxy_load(self):
748 """
749 """
750 cll = ['shed-tools', 'install', '-g', self.args.galaxy_url, '--latest',
751 '-a', self.args.galaxy_api_key , '--name', self.args.tool_name, '--owner','fubar',
752 '--toolshed', self.args.toolshed_url,
753 '--section_label','Generated Tools','--install_tool_dependencies',]
754 print('running\n',' '.join(cll))
755 p = subprocess.run(cll, shell=False)
756 if p.returncode != 0:
757 print('Repository %s installation returned %d' % (self.args.tool_name,p.retcode))
758 else:
759 print('installed %s' % self.args.tool_name)
760 return p.returncode
761
762 def writeShedyml(self):
763 yuser = self.args.user_email.split('@')[0]
764 yfname = os.path.join(self.tooloutdir,'.shed.yml')
765 yamlf = open(yfname, 'w')
766 odict = {'name':self.tool_name,'owner':yuser,'type':'unrestricted','description':self.args.tool_desc}
767 yaml.dump(odict, yamlf, allow_unicode=True)
768 yamlf.close()
769
770 def main():
771 """
772 This is a Galaxy wrapper. It expects to be called by a special purpose tool.xml as:
773 <command interpreter="python">rgBaseScriptWrapper.py --script_path "$scriptPath" --tool_name "foo" --interpreter "Rscript"
774 </command>
775 """
776 parser = argparse.ArgumentParser()
777 a = parser.add_argument
778 a("--script_path", default="")
779 a("--dependencies", default="")
780 a("--cl_override", default="")
781 a("--tool_name", default=None)
782 a("--interpreter_name", default=None)
783 a("--interpreter_version", default=None)
784 a("--exe_package", default=None)
785 a("--exe_package_version", default=None)
786 a("--input_files", default=[], action="append")
787 a("--output_files", default=[], action="append")
788 a("--user_email", default="Unknown")
789 a("--bad_user", default=None)
790 a("--make_Tool", default=None)
791 a("--help_text", default=None)
792 a("--tool_desc", default=None)
793 a("--tool_version", default=None)
794 a("--citations", default=None)
795 a("--additional_parameters", action="append", default=[])
796 a("--edit_additional_parameters", action="store_true", default=False)
797 a("--parampass", default="positional")
798 a("--tfout", default="./tfout")
799 a("--new_tool", default="new_tool")
800 a("--runmode", default=None)
801 a("--galaxy_url", default="http://localhost")
802 a("--galaxy_api_key", default='fakekey')
803 a("--toolshed_url", default="http://localhost:9009")
804 a("--toolshed_api_key", default=None)
805 a("--planemo_test", default="yes")
806
807 args = parser.parse_args()
808 assert not args.bad_user, (
809 'UNAUTHORISED: %s is NOT authorized to use this tool until Galaxy admin adds %s to "admin_users" in the Galaxy configuration file'
810 % (args.bad_user, args.bad_user)
811 )
812 assert (
813 args.tool_name
814 ), "## Tool Factory expects a tool name - eg --tool_name=DESeq"
815 assert (
816 args.interpreter_name or args.exe_package
817 ), "## Tool Factory wrapper expects an interpreter or an executable package"
818 assert args.exe_package or (
819 len(args.script_path) > 0 and os.path.isfile(args.script_path)
820 ), "## Tool Factory wrapper expects a script path - eg --script_path=foo.R if no executable"
821 args.input_files = [
822 x.replace('"', "").replace("'", "") for x in args.input_files
823 ]
824 # remove quotes we need to deal with spaces in CL params
825 for i, x in enumerate(args.additional_parameters):
826 args.additional_parameters[i] = args.additional_parameters[i].replace(
827 '"', ""
828 )
829 r = ScriptRunner(args)
830 if args.make_Tool:
831 r.writeShedyml()
832 retcode = r.makeTooltar()
833 if retcode == 0:
834 if args.planemo_test == "yes":
835 r.install_load()
836 else:
837 retcode = r.run()
838 # if retcode:
839 # sys.exit(retcode) # indicate failure to job runner
840
841
842 if __name__ == "__main__":
843 main()