| 69 | 1 #!/usr/bin/python3 | 
| 48 | 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 | 
| 49 | 9 # suggestions for improvement and bug fixes welcome at | 
|  | 10 # https://github.com/fubar2/toolfactory | 
| 48 | 11 # | 
|  | 12 # July 2020: BCC was fun and I feel like rip van winkle after 5 years. | 
|  | 13 # Decided to | 
|  | 14 # 1. Fix the toolfactory so it works - done for simplest case | 
|  | 15 # 2. Fix planemo so the toolfactory function works | 
|  | 16 # 3. Rewrite bits using galaxyxml functions where that makes sense - done | 
|  | 17 # | 
|  | 18 # removed all the old complications including making the new tool use this same script | 
|  | 19 # galaxyxml now generates the tool xml https://github.com/hexylena/galaxyxml | 
|  | 20 # No support for automatic HTML file creation from arbitrary outputs | 
|  | 21 # essential problem is to create two command lines - one for the tool xml and a different | 
|  | 22 # one to run the executable with the supplied test data and settings | 
|  | 23 # Be simpler to write the tool, then run it with planemo and soak up the test outputs. | 
|  | 24 | 
|  | 25 | 
|  | 26 import argparse | 
| 76 | 27 import datetime | 
|  | 28 import json | 
| 48 | 29 import logging | 
|  | 30 import os | 
|  | 31 import re | 
|  | 32 import shutil | 
|  | 33 import subprocess | 
|  | 34 import sys | 
|  | 35 import tarfile | 
|  | 36 import tempfile | 
|  | 37 import time | 
|  | 38 | 
| 75 | 39 | 
| 63 | 40 from bioblend import toolshed | 
|  | 41 | 
| 48 | 42 import galaxyxml.tool as gxt | 
|  | 43 import galaxyxml.tool.parameters as gxtp | 
|  | 44 | 
|  | 45 import lxml | 
|  | 46 | 
|  | 47 import yaml | 
|  | 48 | 
|  | 49 myversion = "V2.1 July 2020" | 
|  | 50 verbose = True | 
|  | 51 debug = True | 
|  | 52 toolFactoryURL = "https://github.com/fubar2/toolfactory" | 
|  | 53 ourdelim = "~~~" | 
| 50 | 54 ALOT = 10000000  # srsly. command or test overrides use read() so just in case | 
| 49 | 55 STDIOXML = """<stdio> | 
|  | 56 <exit_code range="100:" level="debug" description="shite happens" /> | 
|  | 57 </stdio>""" | 
| 48 | 58 | 
|  | 59 # --input_files="$input_files~~~$CL~~~$input_formats~~~$input_label | 
|  | 60 # ~~~$input_help" | 
|  | 61 IPATHPOS = 0 | 
|  | 62 ICLPOS = 1 | 
|  | 63 IFMTPOS = 2 | 
|  | 64 ILABPOS = 3 | 
|  | 65 IHELPOS = 4 | 
|  | 66 IOCLPOS = 5 | 
|  | 67 | 
| 49 | 68 # --output_files "$otab.history_name~~~$otab.history_format~~~$otab.CL~~~otab.history_test | 
| 48 | 69 ONAMEPOS = 0 | 
|  | 70 OFMTPOS = 1 | 
|  | 71 OCLPOS = 2 | 
| 49 | 72 OTESTPOS = 3 | 
|  | 73 OOCLPOS = 4 | 
|  | 74 | 
| 48 | 75 | 
|  | 76 # --additional_parameters="$i.param_name~~~$i.param_value~~~ | 
|  | 77 # $i.param_label~~~$i.param_help~~~$i.param_type~~~$i.CL~~~i$.param_CLoverride" | 
|  | 78 ANAMEPOS = 0 | 
|  | 79 AVALPOS = 1 | 
|  | 80 ALABPOS = 2 | 
|  | 81 AHELPPOS = 3 | 
|  | 82 ATYPEPOS = 4 | 
|  | 83 ACLPOS = 5 | 
|  | 84 AOVERPOS = 6 | 
|  | 85 AOCLPOS = 7 | 
|  | 86 | 
|  | 87 | 
|  | 88 foo = len(lxml.__version__) | 
|  | 89 # fug you, flake8. Say my name! | 
| 49 | 90 FAKEEXE = "~~~REMOVE~~~ME~~~" | 
|  | 91 # need this until a PR/version bump to fix galaxyxml prepending the exe even | 
|  | 92 # with override. | 
|  | 93 | 
| 48 | 94 | 
|  | 95 def timenow(): | 
| 75 | 96     """return current time as a string""" | 
| 48 | 97     return time.strftime("%d/%m/%Y %H:%M:%S", time.localtime(time.time())) | 
|  | 98 | 
|  | 99 | 
|  | 100 def quote_non_numeric(s): | 
|  | 101     """return a prequoted string for non-numerics | 
|  | 102     useful for perl and Rscript parameter passing? | 
|  | 103     """ | 
|  | 104     try: | 
|  | 105         _ = float(s) | 
|  | 106         return s | 
|  | 107     except ValueError: | 
|  | 108         return '"%s"' % s | 
|  | 109 | 
|  | 110 | 
|  | 111 html_escape_table = {"&": "&", ">": ">", "<": "<", "$": r"\$"} | 
|  | 112 | 
|  | 113 | 
|  | 114 def html_escape(text): | 
|  | 115     """Produce entities within text.""" | 
|  | 116     return "".join(html_escape_table.get(c, c) for c in text) | 
|  | 117 | 
|  | 118 | 
|  | 119 def html_unescape(text): | 
|  | 120     """Revert entities within text. Multiple character targets so use replace""" | 
|  | 121     t = text.replace("&", "&") | 
|  | 122     t = t.replace(">", ">") | 
|  | 123     t = t.replace("<", "<") | 
|  | 124     t = t.replace("\\$", "$") | 
|  | 125     return t | 
|  | 126 | 
|  | 127 | 
|  | 128 def parse_citations(citations_text): | 
| 75 | 129     """""" | 
| 48 | 130     citations = [c for c in citations_text.split("**ENTRY**") if c.strip()] | 
|  | 131     citation_tuples = [] | 
|  | 132     for citation in citations: | 
|  | 133         if citation.startswith("doi"): | 
|  | 134             citation_tuples.append(("doi", citation[len("doi") :].strip())) | 
|  | 135         else: | 
| 49 | 136             citation_tuples.append(("bibtex", citation[len("bibtex") :].strip())) | 
| 48 | 137     return citation_tuples | 
|  | 138 | 
|  | 139 | 
|  | 140 class ScriptRunner: | 
|  | 141     """Wrapper for an arbitrary script | 
|  | 142     uses galaxyxml | 
|  | 143 | 
|  | 144     """ | 
|  | 145 | 
|  | 146     def __init__(self, args=None): | 
|  | 147         """ | 
|  | 148         prepare command line cl for running the tool here | 
|  | 149         and prepare elements needed for galaxyxml tool generation | 
|  | 150         """ | 
|  | 151         self.infiles = [x.split(ourdelim) for x in args.input_files] | 
|  | 152         self.outfiles = [x.split(ourdelim) for x in args.output_files] | 
|  | 153         self.addpar = [x.split(ourdelim) for x in args.additional_parameters] | 
|  | 154         self.args = args | 
|  | 155         self.cleanuppar() | 
|  | 156         self.lastclredirect = None | 
|  | 157         self.lastxclredirect = None | 
|  | 158         self.cl = [] | 
|  | 159         self.xmlcl = [] | 
|  | 160         self.is_positional = self.args.parampass == "positional" | 
| 63 | 161         if self.args.sysexe: | 
| 49 | 162             self.executeme = self.args.sysexe | 
| 63 | 163         else: | 
|  | 164             if self.args.packages: | 
|  | 165                 self.executeme = self.args.packages.split(",")[0].split(":")[0] | 
|  | 166             else: | 
|  | 167                 self.executeme = None | 
| 48 | 168         aCL = self.cl.append | 
| 49 | 169         aXCL = self.xmlcl.append | 
| 48 | 170         assert args.parampass in [ | 
|  | 171             "0", | 
|  | 172             "argparse", | 
|  | 173             "positional", | 
| 49 | 174         ], 'args.parampass must be "0","positional" or "argparse"' | 
| 48 | 175         self.tool_name = re.sub("[^a-zA-Z0-9_]+", "", args.tool_name) | 
|  | 176         self.tool_id = self.tool_name | 
| 50 | 177         self.newtool = gxt.Tool( | 
| 48 | 178             self.args.tool_name, | 
|  | 179             self.tool_id, | 
|  | 180             self.args.tool_version, | 
|  | 181             self.args.tool_desc, | 
| 50 | 182             FAKEEXE, | 
| 48 | 183         ) | 
| 76 | 184         self.newtarpath = "toolfactory_%s.tgz" % self.tool_name | 
| 48 | 185         self.tooloutdir = "tfout" | 
|  | 186         self.repdir = "TF_run_report_tempdir" | 
|  | 187         self.testdir = os.path.join(self.tooloutdir, "test-data") | 
|  | 188         if not os.path.exists(self.tooloutdir): | 
|  | 189             os.mkdir(self.tooloutdir) | 
|  | 190         if not os.path.exists(self.testdir): | 
|  | 191             os.mkdir(self.testdir)  # make tests directory | 
|  | 192         if not os.path.exists(self.repdir): | 
|  | 193             os.mkdir(self.repdir) | 
|  | 194         self.tinputs = gxtp.Inputs() | 
|  | 195         self.toutputs = gxtp.Outputs() | 
|  | 196         self.testparam = [] | 
| 49 | 197         if self.args.script_path: | 
|  | 198             self.prepScript() | 
|  | 199         if self.args.command_override: | 
|  | 200             scos = open(self.args.command_override, "r").readlines() | 
|  | 201             self.command_override = [x.rstrip() for x in scos] | 
|  | 202         else: | 
|  | 203             self.command_override = None | 
|  | 204         if self.args.test_override: | 
|  | 205             stos = open(self.args.test_override, "r").readlines() | 
|  | 206             self.test_override = [x.rstrip() for x in stos] | 
|  | 207         else: | 
|  | 208             self.test_override = None | 
| 50 | 209         if self.args.cl_prefix:  # DIY CL start | 
| 49 | 210             clp = self.args.cl_prefix.split(" ") | 
|  | 211             for c in clp: | 
|  | 212                 aCL(c) | 
|  | 213                 aXCL(c) | 
|  | 214         else: | 
| 56 | 215             if self.args.script_path: | 
|  | 216                 aCL(self.executeme) | 
|  | 217                 aCL(self.sfile) | 
|  | 218                 aXCL(self.executeme) | 
|  | 219                 aXCL("$runme") | 
| 48 | 220             else: | 
| 56 | 221                 aCL(self.executeme)  # this little CL will just run | 
|  | 222                 aXCL(self.executeme) | 
| 50 | 223         self.elog = os.path.join(self.repdir, "%s_error_log.txt" % self.tool_name) | 
|  | 224         self.tlog = os.path.join(self.repdir, "%s_runner_log.txt" % self.tool_name) | 
| 48 | 225 | 
|  | 226         if self.args.parampass == "0": | 
|  | 227             self.clsimple() | 
|  | 228         else: | 
|  | 229             clsuffix = [] | 
|  | 230             xclsuffix = [] | 
|  | 231             for i, p in enumerate(self.infiles): | 
|  | 232                 if p[IOCLPOS] == "STDIN": | 
|  | 233                     appendme = [ | 
|  | 234                         p[IOCLPOS], | 
|  | 235                         p[ICLPOS], | 
|  | 236                         p[IPATHPOS], | 
|  | 237                         "< %s" % p[IPATHPOS], | 
|  | 238                     ] | 
|  | 239                     xappendme = [ | 
|  | 240                         p[IOCLPOS], | 
|  | 241                         p[ICLPOS], | 
|  | 242                         p[IPATHPOS], | 
|  | 243                         "< $%s" % p[ICLPOS], | 
|  | 244                     ] | 
|  | 245                 else: | 
|  | 246                     appendme = [p[IOCLPOS], p[ICLPOS], p[IPATHPOS], ""] | 
|  | 247                     xappendme = [p[IOCLPOS], p[ICLPOS], "$%s" % p[ICLPOS], ""] | 
|  | 248                 clsuffix.append(appendme) | 
|  | 249                 xclsuffix.append(xappendme) | 
|  | 250             for i, p in enumerate(self.outfiles): | 
|  | 251                 if p[OOCLPOS] == "STDOUT": | 
|  | 252                     self.lastclredirect = [">", p[ONAMEPOS]] | 
|  | 253                     self.lastxclredirect = [">", "$%s" % p[OCLPOS]] | 
|  | 254                 else: | 
|  | 255                     clsuffix.append([p[OOCLPOS], p[OCLPOS], p[ONAMEPOS], ""]) | 
| 49 | 256                     xclsuffix.append([p[OOCLPOS], p[OCLPOS], "$%s" % p[ONAMEPOS], ""]) | 
| 48 | 257             for p in self.addpar: | 
| 49 | 258                 clsuffix.append([p[AOCLPOS], p[ACLPOS], p[AVALPOS], p[AOVERPOS]]) | 
| 48 | 259                 xclsuffix.append( | 
|  | 260                     [p[AOCLPOS], p[ACLPOS], '"$%s"' % p[ANAMEPOS], p[AOVERPOS]] | 
|  | 261                 ) | 
|  | 262             clsuffix.sort() | 
|  | 263             xclsuffix.sort() | 
|  | 264             self.xclsuffix = xclsuffix | 
|  | 265             self.clsuffix = clsuffix | 
|  | 266             if self.args.parampass == "positional": | 
|  | 267                 self.clpositional() | 
|  | 268             else: | 
|  | 269                 self.clargparse() | 
|  | 270 | 
|  | 271     def prepScript(self): | 
|  | 272         rx = open(self.args.script_path, "r").readlines() | 
|  | 273         rx = [x.rstrip() for x in rx] | 
|  | 274         rxcheck = [x.strip() for x in rx if x.strip() > ""] | 
|  | 275         assert len(rxcheck) > 0, "Supplied script is empty. Cannot run" | 
|  | 276         self.script = "\n".join(rx) | 
|  | 277         fhandle, self.sfile = tempfile.mkstemp( | 
| 49 | 278             prefix=self.tool_name, suffix="_%s" % (self.executeme) | 
| 48 | 279         ) | 
|  | 280         tscript = open(self.sfile, "w") | 
|  | 281         tscript.write(self.script) | 
|  | 282         tscript.close() | 
| 49 | 283         self.indentedScript = "  %s" % "\n".join([" %s" % html_escape(x) for x in rx]) | 
|  | 284         self.escapedScript = "%s" % "\n".join([" %s" % html_escape(x) for x in rx]) | 
|  | 285         art = "%s.%s" % (self.tool_name, self.executeme) | 
| 48 | 286         artifact = open(art, "wb") | 
|  | 287         artifact.write(bytes(self.script, "utf8")) | 
|  | 288         artifact.close() | 
|  | 289 | 
|  | 290     def cleanuppar(self): | 
|  | 291         """ positional parameters are complicated by their numeric ordinal""" | 
|  | 292         for i, p in enumerate(self.infiles): | 
|  | 293             if self.args.parampass == "positional": | 
| 75 | 294                 assert p[ | 
|  | 295                     ICLPOS | 
|  | 296                 ].isdigit(), "Positional parameters must be ordinal integers - got %s for %s" % ( | 
|  | 297                     p[ICLPOS], | 
|  | 298                     p[ILABPOS], | 
| 48 | 299                 ) | 
|  | 300             p.append(p[ICLPOS]) | 
|  | 301             if p[ICLPOS].isdigit() or self.args.parampass == "0": | 
|  | 302                 scl = "input%d" % (i + 1) | 
|  | 303                 p[ICLPOS] = scl | 
|  | 304             self.infiles[i] = p | 
|  | 305         for i, p in enumerate( | 
|  | 306             self.outfiles | 
|  | 307         ):  # trying to automagically gather using extensions | 
|  | 308             if self.args.parampass == "positional" and p[OCLPOS] != "STDOUT": | 
| 75 | 309                 assert p[ | 
|  | 310                     OCLPOS | 
|  | 311                 ].isdigit(), "Positional parameters must be ordinal integers - got %s for %s" % ( | 
|  | 312                     p[OCLPOS], | 
|  | 313                     p[ONAMEPOS], | 
| 48 | 314                 ) | 
|  | 315             p.append(p[OCLPOS]) | 
|  | 316             if p[OCLPOS].isdigit() or p[OCLPOS] == "STDOUT": | 
|  | 317                 scl = p[ONAMEPOS] | 
|  | 318                 p[OCLPOS] = scl | 
|  | 319             self.outfiles[i] = p | 
|  | 320         for i, p in enumerate(self.addpar): | 
|  | 321             if self.args.parampass == "positional": | 
| 75 | 322                 assert p[ | 
|  | 323                     ACLPOS | 
|  | 324                 ].isdigit(), "Positional parameters must be ordinal integers - got %s for %s" % ( | 
|  | 325                     p[ACLPOS], | 
|  | 326                     p[ANAMEPOS], | 
| 48 | 327                 ) | 
|  | 328             p.append(p[ACLPOS]) | 
|  | 329             if p[ACLPOS].isdigit(): | 
|  | 330                 scl = "input%s" % p[ACLPOS] | 
|  | 331                 p[ACLPOS] = scl | 
|  | 332             self.addpar[i] = p | 
|  | 333 | 
|  | 334     def clsimple(self): | 
| 75 | 335         """no parameters - uses < and > for i/o""" | 
| 48 | 336         aCL = self.cl.append | 
|  | 337         aXCL = self.xmlcl.append | 
| 62 | 338 | 
|  | 339         if len(self.infiles) > 0: | 
|  | 340             aCL("<") | 
|  | 341             aCL(self.infiles[0][IPATHPOS]) | 
|  | 342             aXCL("<") | 
|  | 343             aXCL("$%s" % self.infiles[0][ICLPOS]) | 
|  | 344         if len(self.outfiles) > 0: | 
|  | 345             aCL(">") | 
|  | 346             aCL(self.outfiles[0][OCLPOS]) | 
|  | 347             aXCL(">") | 
|  | 348             aXCL("$%s" % self.outfiles[0][ONAMEPOS]) | 
| 48 | 349 | 
|  | 350     def clpositional(self): | 
|  | 351         # inputs in order then params | 
|  | 352         aCL = self.cl.append | 
|  | 353         for (o_v, k, v, koverride) in self.clsuffix: | 
|  | 354             if " " in v: | 
|  | 355                 aCL("%s" % v) | 
|  | 356             else: | 
|  | 357                 aCL(v) | 
|  | 358         aXCL = self.xmlcl.append | 
|  | 359         for (o_v, k, v, koverride) in self.xclsuffix: | 
|  | 360             aXCL(v) | 
|  | 361         if self.lastxclredirect: | 
|  | 362             aXCL(self.lastxclredirect[0]) | 
|  | 363             aXCL(self.lastxclredirect[1]) | 
|  | 364 | 
|  | 365     def clargparse(self): | 
| 75 | 366         """argparse style""" | 
| 48 | 367         aCL = self.cl.append | 
|  | 368         aXCL = self.xmlcl.append | 
|  | 369         # inputs then params in argparse named form | 
|  | 370         for (o_v, k, v, koverride) in self.xclsuffix: | 
|  | 371             if koverride > "": | 
|  | 372                 k = koverride | 
|  | 373             elif len(k.strip()) == 1: | 
|  | 374                 k = "-%s" % k | 
|  | 375             else: | 
|  | 376                 k = "--%s" % k | 
|  | 377             aXCL(k) | 
|  | 378             aXCL(v) | 
|  | 379         for (o_v, k, v, koverride) in self.clsuffix: | 
|  | 380             if koverride > "": | 
|  | 381                 k = koverride | 
|  | 382             elif len(k.strip()) == 1: | 
|  | 383                 k = "-%s" % k | 
|  | 384             else: | 
|  | 385                 k = "--%s" % k | 
|  | 386             aCL(k) | 
|  | 387             aCL(v) | 
|  | 388 | 
|  | 389     def getNdash(self, newname): | 
|  | 390         if self.is_positional: | 
|  | 391             ndash = 0 | 
|  | 392         else: | 
|  | 393             ndash = 2 | 
|  | 394             if len(newname) < 2: | 
|  | 395                 ndash = 1 | 
|  | 396         return ndash | 
|  | 397 | 
|  | 398     def doXMLparam(self): | 
|  | 399         """flake8 made me do this...""" | 
|  | 400         for p in self.outfiles: | 
| 49 | 401             newname, newfmt, newcl, test, oldcl = p | 
| 48 | 402             ndash = self.getNdash(newcl) | 
|  | 403             aparm = gxtp.OutputData(newcl, format=newfmt, num_dashes=ndash) | 
|  | 404             aparm.positional = self.is_positional | 
|  | 405             if self.is_positional: | 
|  | 406                 if oldcl == "STDOUT": | 
|  | 407                     aparm.positional = 9999999 | 
|  | 408                     aparm.command_line_override = "> $%s" % newcl | 
|  | 409                 else: | 
|  | 410                     aparm.positional = int(oldcl) | 
|  | 411                     aparm.command_line_override = "$%s" % newcl | 
|  | 412             self.toutputs.append(aparm) | 
| 49 | 413             usetest = None | 
|  | 414             ld = None | 
| 50 | 415             if test > "": | 
|  | 416                 if test.startswith("diff"): | 
|  | 417                     usetest = "diff" | 
|  | 418                     if test.split(":")[1].isdigit: | 
|  | 419                         ld = int(test.split(":")[1]) | 
| 49 | 420                 else: | 
|  | 421                     usetest = test | 
| 50 | 422             tp = gxtp.TestOutput( | 
|  | 423                 name=newcl, | 
|  | 424                 value="%s_sample" % newcl, | 
|  | 425                 format=newfmt, | 
|  | 426                 compare=usetest, | 
|  | 427                 lines_diff=ld, | 
|  | 428                 delta=None, | 
|  | 429             ) | 
| 48 | 430             self.testparam.append(tp) | 
|  | 431         for p in self.infiles: | 
|  | 432             newname = p[ICLPOS] | 
|  | 433             newfmt = p[IFMTPOS] | 
|  | 434             ndash = self.getNdash(newname) | 
|  | 435             if not len(p[ILABPOS]) > 0: | 
|  | 436                 alab = p[ICLPOS] | 
|  | 437             else: | 
|  | 438                 alab = p[ILABPOS] | 
|  | 439             aninput = gxtp.DataParam( | 
|  | 440                 newname, | 
|  | 441                 optional=False, | 
|  | 442                 label=alab, | 
|  | 443                 help=p[IHELPOS], | 
|  | 444                 format=newfmt, | 
|  | 445                 multiple=False, | 
|  | 446                 num_dashes=ndash, | 
|  | 447             ) | 
|  | 448             aninput.positional = self.is_positional | 
|  | 449             self.tinputs.append(aninput) | 
|  | 450             tparm = gxtp.TestParam(name=newname, value="%s_sample" % newname) | 
|  | 451             self.testparam.append(tparm) | 
|  | 452         for p in self.addpar: | 
|  | 453             newname, newval, newlabel, newhelp, newtype, newcl, override, oldcl = p | 
|  | 454             if not len(newlabel) > 0: | 
|  | 455                 newlabel = newname | 
|  | 456             ndash = self.getNdash(newname) | 
|  | 457             if newtype == "text": | 
|  | 458                 aparm = gxtp.TextParam( | 
|  | 459                     newname, | 
|  | 460                     label=newlabel, | 
|  | 461                     help=newhelp, | 
|  | 462                     value=newval, | 
|  | 463                     num_dashes=ndash, | 
|  | 464                 ) | 
|  | 465             elif newtype == "integer": | 
|  | 466                 aparm = gxtp.IntegerParam( | 
|  | 467                     newname, | 
|  | 468                     label=newname, | 
|  | 469                     help=newhelp, | 
|  | 470                     value=newval, | 
|  | 471                     num_dashes=ndash, | 
|  | 472                 ) | 
|  | 473             elif newtype == "float": | 
|  | 474                 aparm = gxtp.FloatParam( | 
|  | 475                     newname, | 
|  | 476                     label=newname, | 
|  | 477                     help=newhelp, | 
|  | 478                     value=newval, | 
|  | 479                     num_dashes=ndash, | 
|  | 480                 ) | 
|  | 481             else: | 
|  | 482                 raise ValueError( | 
|  | 483                     'Unrecognised parameter type "%s" for\ | 
|  | 484                  additional parameter %s in makeXML' | 
|  | 485                     % (newtype, newname) | 
|  | 486                 ) | 
|  | 487             aparm.positional = self.is_positional | 
|  | 488             if self.is_positional: | 
| 63 | 489                 aparm.positional = int(oldcl) | 
| 48 | 490             self.tinputs.append(aparm) | 
| 63 | 491             tparm = gxtp.TestParam(newname, value=newval) | 
| 48 | 492             self.testparam.append(tparm) | 
|  | 493 | 
|  | 494     def doNoXMLparam(self): | 
| 49 | 495         """filter style package - stdin to stdout""" | 
| 62 | 496         if len(self.infiles) > 0: | 
|  | 497             alab = self.infiles[0][ILABPOS] | 
|  | 498             if len(alab) == 0: | 
|  | 499                 alab = self.infiles[0][ICLPOS] | 
|  | 500             max1s = ( | 
|  | 501                 "Maximum one input if parampass is 0 but multiple input files supplied - %s" | 
|  | 502                 % str(self.infiles) | 
|  | 503             ) | 
|  | 504             assert len(self.infiles) == 1, max1s | 
|  | 505             newname = self.infiles[0][ICLPOS] | 
|  | 506             aninput = gxtp.DataParam( | 
|  | 507                 newname, | 
|  | 508                 optional=False, | 
|  | 509                 label=alab, | 
|  | 510                 help=self.infiles[0][IHELPOS], | 
|  | 511                 format=self.infiles[0][IFMTPOS], | 
|  | 512                 multiple=False, | 
|  | 513                 num_dashes=0, | 
|  | 514             ) | 
|  | 515             aninput.command_line_override = "< $%s" % newname | 
|  | 516             aninput.positional = self.is_positional | 
|  | 517             self.tinputs.append(aninput) | 
|  | 518             tp = gxtp.TestParam(name=newname, value="%s_sample" % newname) | 
|  | 519             self.testparam.append(tp) | 
| 63 | 520         if len(self.outfiles) > 0: | 
| 62 | 521             newname = self.outfiles[0][OCLPOS] | 
|  | 522             newfmt = self.outfiles[0][OFMTPOS] | 
|  | 523             anout = gxtp.OutputData(newname, format=newfmt, num_dashes=0) | 
|  | 524             anout.command_line_override = "> $%s" % newname | 
|  | 525             anout.positional = self.is_positional | 
|  | 526             self.toutputs.append(anout) | 
| 75 | 527             tp = gxtp.TestOutput( | 
|  | 528                 name=newname, value="%s_sample" % newname, format=newfmt | 
|  | 529             ) | 
| 62 | 530             self.testparam.append(tp) | 
| 48 | 531 | 
|  | 532     def makeXML(self): | 
|  | 533         """ | 
|  | 534         Create a Galaxy xml tool wrapper for the new script | 
|  | 535         Uses galaxyhtml | 
|  | 536         Hmmm. How to get the command line into correct order... | 
|  | 537         """ | 
| 49 | 538         if self.command_override: | 
| 56 | 539             self.newtool.command_override = self.command_override  # config file | 
| 48 | 540         else: | 
| 56 | 541             self.newtool.command_override = self.xmlcl | 
| 48 | 542         if self.args.help_text: | 
|  | 543             helptext = open(self.args.help_text, "r").readlines() | 
| 50 | 544             safertext = [html_escape(x) for x in helptext] | 
| 63 | 545             if False and self.args.script_path: | 
| 75 | 546                 scrp = self.script.split("\n") | 
|  | 547                 scrpt = ["   %s" % x for x in scrp]  # try to stop templating | 
|  | 548                 scrpt.insert(0, "```\n") | 
| 50 | 549                 if len(scrpt) > 300: | 
| 75 | 550                     safertext = ( | 
|  | 551                         safertext + scrpt[:100] + \ | 
|  | 552                         [">500 lines - stuff deleted", "......"] + scrpt[-100:] | 
|  | 553                     ) | 
| 50 | 554                 else: | 
|  | 555                     safertext = safertext + scrpt | 
|  | 556                 safertext.append("\n```") | 
| 62 | 557             self.newtool.help = "\n".join([x for x in safertext]) | 
| 48 | 558         else: | 
| 50 | 559             self.newtool.help = ( | 
| 48 | 560                 "Please ask the tool author (%s) for help \ | 
|  | 561               as none was supplied at tool generation\n" | 
|  | 562                 % (self.args.user_email) | 
|  | 563             ) | 
| 50 | 564         self.newtool.version_command = None  # do not want | 
| 48 | 565         requirements = gxtp.Requirements() | 
| 49 | 566         if self.args.packages: | 
|  | 567             for d in self.args.packages.split(","): | 
|  | 568                 if ":" in d: | 
|  | 569                     packg, ver = d.split(":") | 
|  | 570                 else: | 
|  | 571                     packg = d | 
|  | 572                     ver = "" | 
| 50 | 573                 requirements.append( | 
|  | 574                     gxtp.Requirement("package", packg.strip(), ver.strip()) | 
|  | 575                 ) | 
|  | 576         self.newtool.requirements = requirements | 
| 48 | 577         if self.args.parampass == "0": | 
|  | 578             self.doNoXMLparam() | 
|  | 579         else: | 
|  | 580             self.doXMLparam() | 
| 50 | 581         self.newtool.outputs = self.toutputs | 
|  | 582         self.newtool.inputs = self.tinputs | 
|  | 583         if self.args.script_path: | 
| 48 | 584             configfiles = gxtp.Configfiles() | 
| 49 | 585             configfiles.append(gxtp.Configfile(name="runme", text=self.script)) | 
| 50 | 586             self.newtool.configfiles = configfiles | 
| 48 | 587         tests = gxtp.Tests() | 
|  | 588         test_a = gxtp.Test() | 
|  | 589         for tp in self.testparam: | 
|  | 590             test_a.append(tp) | 
|  | 591         tests.append(test_a) | 
| 50 | 592         self.newtool.tests = tests | 
|  | 593         self.newtool.add_comment( | 
| 48 | 594             "Created by %s at %s using the Galaxy Tool Factory." | 
|  | 595             % (self.args.user_email, timenow()) | 
|  | 596         ) | 
| 50 | 597         self.newtool.add_comment("Source in git at: %s" % (toolFactoryURL)) | 
|  | 598         self.newtool.add_comment( | 
| 48 | 599             "Cite: Creating re-usable tools from scripts doi: \ | 
|  | 600             10.1093/bioinformatics/bts573" | 
|  | 601         ) | 
| 50 | 602         exml0 = self.newtool.export() | 
| 49 | 603         exml = exml0.replace(FAKEEXE, "")  # temporary work around until PR accepted | 
| 50 | 604         if ( | 
|  | 605             self.test_override | 
|  | 606         ):  # cannot do this inside galaxyxml as it expects lxml objects for tests | 
|  | 607             part1 = exml.split("<tests>")[0] | 
|  | 608             part2 = exml.split("</tests>")[1] | 
|  | 609             fixed = "%s\n%s\n%s" % (part1, self.test_override, part2) | 
| 49 | 610             exml = fixed | 
| 63 | 611         exml = exml.replace('range="1:"', 'range="1000:"') | 
| 49 | 612         xf = open("%s.xml" % self.tool_name, "w") | 
| 48 | 613         xf.write(exml) | 
|  | 614         xf.write("\n") | 
|  | 615         xf.close() | 
|  | 616         # ready for the tarball | 
|  | 617 | 
|  | 618     def run(self): | 
|  | 619         """ | 
| 50 | 620         generate test outputs by running a command line | 
| 56 | 621         won't work if command or test override in play - planemo is the | 
| 50 | 622         easiest way to generate test outputs for that case so is | 
|  | 623         automagically selected | 
| 48 | 624         """ | 
|  | 625         scl = " ".join(self.cl) | 
|  | 626         err = None | 
|  | 627         if self.args.parampass != "0": | 
| 56 | 628             if os.path.exists(self.elog): | 
|  | 629                 ste = open(self.elog, "a") | 
|  | 630             else: | 
|  | 631                 ste = open(self.elog, "w") | 
| 48 | 632             if self.lastclredirect: | 
| 49 | 633                 sto = open(self.lastclredirect[1], "wb")  # is name of an output file | 
| 48 | 634             else: | 
| 56 | 635                 if os.path.exists(self.tlog): | 
|  | 636                     sto = open(self.tlog, "a") | 
|  | 637                 else: | 
|  | 638                     sto = open(self.tlog, "w") | 
| 48 | 639                 sto.write( | 
| 75 | 640                     "## Executing Toolfactory generated command line = %s\n" % scl | 
| 48 | 641                 ) | 
|  | 642             sto.flush() | 
|  | 643             p = subprocess.run(self.cl, shell=False, stdout=sto, stderr=ste) | 
|  | 644             sto.close() | 
|  | 645             ste.close() | 
|  | 646             retval = p.returncode | 
| 49 | 647         else:  # work around special case - stdin and write to stdout | 
| 62 | 648             if len(self.infiles) > 0: | 
|  | 649                 sti = open(self.infiles[0][IPATHPOS], "rb") | 
|  | 650             else: | 
| 63 | 651                 sti = sys.stdin | 
| 62 | 652             if len(self.outfiles) > 0: | 
|  | 653                 sto = open(self.outfiles[0][ONAMEPOS], "wb") | 
|  | 654             else: | 
|  | 655                 sto = sys.stdout | 
| 48 | 656             p = subprocess.run(self.cl, shell=False, stdout=sto, stdin=sti) | 
| 75 | 657             sto.write("## Executing Toolfactory generated command line = %s\n" % scl) | 
| 48 | 658             retval = p.returncode | 
|  | 659             sto.close() | 
|  | 660             sti.close() | 
|  | 661         if os.path.isfile(self.tlog) and os.stat(self.tlog).st_size == 0: | 
|  | 662             os.unlink(self.tlog) | 
|  | 663         if os.path.isfile(self.elog) and os.stat(self.elog).st_size == 0: | 
|  | 664             os.unlink(self.elog) | 
|  | 665         if retval != 0 and err:  # problem | 
|  | 666             sys.stderr.write(err) | 
|  | 667         logging.debug("run done") | 
|  | 668         return retval | 
|  | 669 | 
| 63 | 670     def shedLoad(self): | 
| 48 | 671         """ | 
| 63 | 672         {'deleted': False, | 
|  | 673               'description': 'Tools for manipulating data', | 
|  | 674               'id': '175812cd7caaf439', | 
|  | 675               'model_class': 'Category', | 
|  | 676               'name': 'Text Manipulation', | 
|  | 677               'url': '/api/categories/175812cd7caaf439'}] | 
|  | 678 | 
|  | 679 | 
| 48 | 680         """ | 
| 49 | 681         if os.path.exists(self.tlog): | 
| 63 | 682             sto = open(self.tlog, "a") | 
| 48 | 683         else: | 
| 63 | 684             sto = open(self.tlog, "w") | 
| 48 | 685 | 
| 75 | 686         ts = toolshed.ToolShedInstance( | 
|  | 687             url=self.args.toolshed_url, key=self.args.toolshed_api_key, verify=False | 
|  | 688         ) | 
| 63 | 689         repos = ts.repositories.get_repositories() | 
| 75 | 690         rnames = [x.get("name", "?") for x in repos] | 
|  | 691         rids = [x.get("id", "?") for x in repos] | 
|  | 692         sto.write(f"############names={rnames} rids={rids}") | 
|  | 693         cat = "ToolFactory generated tools" | 
| 63 | 694         if self.args.tool_name not in rnames: | 
|  | 695             tscat = ts.categories.get_categories() | 
| 75 | 696             cnames = [x.get("name", "?") for x in tscat] | 
|  | 697             cids = [x.get("id", "?") for x in tscat] | 
| 63 | 698             catID = None | 
|  | 699             if cat in cnames: | 
|  | 700                 ci = cnames.index(cat) | 
|  | 701                 catID = cids[ci] | 
| 75 | 702             res = ts.repositories.create_repository( | 
|  | 703                 name=self.args.tool_name, | 
|  | 704                 synopsis="Synopsis:%s" % self.args.tool_desc, | 
|  | 705                 description=self.args.tool_desc, | 
|  | 706                 type="unrestricted", | 
|  | 707                 remote_repository_url=self.args.toolshed_url, | 
|  | 708                 homepage_url=None, | 
|  | 709                 category_ids=catID, | 
|  | 710             ) | 
|  | 711             tid = res.get("id", None) | 
|  | 712             sto.write(f"##########create res={res}") | 
| 49 | 713         else: | 
| 75 | 714             i = rnames.index(self.args.tool_name) | 
|  | 715             tid = rids[i] | 
|  | 716         res = ts.repositories.update_repository( | 
|  | 717             id=tid, tar_ball_path=self.newtarpath, commit_message=None | 
|  | 718         ) | 
|  | 719         sto.write(f"#####update res={res}") | 
| 63 | 720         sto.close() | 
|  | 721 | 
| 48 | 722     def eph_galaxy_load(self): | 
| 75 | 723         """load the new tool from the local toolshed after planemo uploads it""" | 
| 49 | 724         if os.path.exists(self.tlog): | 
| 50 | 725             tout = open(self.tlog, "a") | 
| 49 | 726         else: | 
| 50 | 727             tout = open(self.tlog, "w") | 
| 49 | 728         cll = [ | 
|  | 729             "shed-tools", | 
|  | 730             "install", | 
|  | 731             "-g", | 
|  | 732             self.args.galaxy_url, | 
|  | 733             "--latest", | 
|  | 734             "-a", | 
|  | 735             self.args.galaxy_api_key, | 
|  | 736             "--name", | 
|  | 737             self.args.tool_name, | 
|  | 738             "--owner", | 
|  | 739             "fubar", | 
|  | 740             "--toolshed", | 
|  | 741             self.args.toolshed_url, | 
| 75 | 742             "--section_label", | 
| 63 | 743             "ToolFactory", | 
| 49 | 744         ] | 
| 63 | 745         tout.write("running\n%s\n" % " ".join(cll)) | 
| 49 | 746         p = subprocess.run(cll, shell=False, stderr=tout, stdout=tout) | 
| 75 | 747         tout.write( | 
|  | 748             "installed %s - got retcode %d\n" % (self.args.tool_name, p.returncode) | 
|  | 749         ) | 
| 63 | 750         tout.close() | 
|  | 751         return p.returncode | 
|  | 752 | 
|  | 753     def planemo_shedload(self): | 
|  | 754         """ | 
|  | 755         planemo shed_create --shed_target testtoolshed | 
|  | 756         planemo shed_init --name=<name> | 
|  | 757                   --owner=<shed_username> | 
|  | 758                   --description=<short description> | 
|  | 759                   [--remote_repository_url=<URL to .shed.yml on github>] | 
|  | 760                   [--homepage_url=<Homepage for tool.>] | 
|  | 761                   [--long_description=<long description>] | 
|  | 762                   [--category=<category name>]* | 
| 66 | 763 | 
|  | 764 | 
| 63 | 765         planemo shed_update --check_diff --shed_target testtoolshed | 
|  | 766         """ | 
|  | 767         if os.path.exists(self.tlog): | 
|  | 768             tout = open(self.tlog, "a") | 
| 48 | 769         else: | 
| 63 | 770             tout = open(self.tlog, "w") | 
| 75 | 771         ts = toolshed.ToolShedInstance( | 
|  | 772             url=self.args.toolshed_url, key=self.args.toolshed_api_key, verify=False | 
|  | 773         ) | 
| 63 | 774         repos = ts.repositories.get_repositories() | 
| 75 | 775         rnames = [x.get("name", "?") for x in repos] | 
|  | 776         rids = [x.get("id", "?") for x in repos] | 
|  | 777         tout.write(f"############names={rnames} rids={rids}") | 
|  | 778         #cat = "ToolFactory generated tools" | 
| 63 | 779         if self.args.tool_name not in rnames: | 
| 75 | 780             cll = [ | 
|  | 781                 "planemo", | 
|  | 782                 "shed_create", | 
|  | 783                 "--shed_target", | 
|  | 784                 "local", | 
|  | 785                 "--owner", | 
|  | 786                 "fubar", | 
|  | 787                 "--name", | 
|  | 788                 self.args.tool_name, | 
|  | 789                 "--shed_key", | 
|  | 790                 self.args.toolshed_api_key, | 
|  | 791             ] | 
| 63 | 792             try: | 
|  | 793                 p = subprocess.run( | 
|  | 794                     cll, shell=False, cwd=self.tooloutdir, stdout=tout, stderr=tout | 
|  | 795                 ) | 
|  | 796             except: | 
|  | 797                 pass | 
|  | 798             if p.returncode != 0: | 
| 75 | 799                 tout.write("Repository %s exists" % self.args.tool_name) | 
| 63 | 800             else: | 
|  | 801                 tout.write("initiated %s" % self.args.tool_name) | 
|  | 802         cll = [ | 
|  | 803             "planemo", | 
|  | 804             "shed_upload", | 
|  | 805             "--shed_target", | 
|  | 806             "local", | 
|  | 807             "--owner", | 
|  | 808             "fubar", | 
|  | 809             "--name", | 
|  | 810             self.args.tool_name, | 
|  | 811             "--shed_key", | 
|  | 812             self.args.toolshed_api_key, | 
|  | 813             "--tar", | 
|  | 814             self.newtarpath, | 
|  | 815         ] | 
|  | 816         p = subprocess.run(cll, shell=False, stdout=tout, stderr=tout) | 
| 75 | 817         tout.write("Ran %s got %d" % (" ".join(cll), p.returncode)) | 
| 49 | 818         tout.close() | 
| 48 | 819         return p.returncode | 
|  | 820 | 
| 76 | 821     def eph_test(self, genoutputs=True): | 
|  | 822         """problem getting jobid - ephemeris upload is the job before the one we want - but depends on how many inputs | 
|  | 823         """ | 
| 75 | 824         if os.path.exists(self.tlog): | 
|  | 825             tout = open(self.tlog, "a") | 
|  | 826         else: | 
|  | 827             tout = open(self.tlog, "w") | 
|  | 828         cll = [ | 
|  | 829             "shed-tools", | 
|  | 830             "test", | 
|  | 831             "-g", | 
|  | 832             self.args.galaxy_url, | 
|  | 833             "-a", | 
|  | 834             self.args.galaxy_api_key, | 
|  | 835             "--name", | 
|  | 836             self.args.tool_name, | 
|  | 837             "--owner", | 
|  | 838             "fubar", | 
|  | 839         ] | 
| 76 | 840         if genoutputs: | 
|  | 841             dummy, tfile = tempfile.mkstemp() | 
|  | 842             p = subprocess.run( | 
|  | 843                cll, shell=False, stderr=dummy, stdout=dummy | 
|  | 844             ) | 
|  | 845 | 
|  | 846             with open('tool_test_output.json','rb') as f: | 
|  | 847                 s = json.loads(f.read()) | 
|  | 848                 print('read %s' % s) | 
|  | 849                 cl = s['tests'][0]['data']['job']['command_line'].split() | 
|  | 850                 n = cl.index('--script_path') | 
|  | 851                 jobdir = cl[n+1] | 
|  | 852                 jobdir = jobdir.replace('"','') | 
|  | 853                 jobdir = jobdir.split('/configs')[0] | 
|  | 854                 print('jobdir=%s' % jobdir) | 
|  | 855 | 
|  | 856                 #"/home/ross/galthrow/database/jobs_directory/000/649/configs/tmptfxu51gs\" | 
|  | 857             src = os.path.join(jobdir,'working',self.newtarpath) | 
|  | 858             if os.path.exists(src): | 
|  | 859                 dest = os.path.join(self.testdir, self.newtarpath) | 
|  | 860                 shutil.copyfile(src, dest) | 
|  | 861             else: | 
|  | 862                 tout.write('No toolshed archive found after first ephemeris test - not a good sign') | 
|  | 863             ephouts = os.path.join(jobdir,'working','tfout','test-data') | 
|  | 864             with os.scandir(ephouts) as outs: | 
|  | 865                 for entry in outs: | 
|  | 866                     if not entry.is_file(): | 
|  | 867                         continue | 
|  | 868                     dest = os.path.join(self.tooloutdir, entry.name) | 
|  | 869                     src = os.path.join(ephouts, entry.name) | 
|  | 870                     shutil.copyfile(src, dest) | 
|  | 871         else: | 
|  | 872             p = subprocess.run( | 
|  | 873                cll, shell=False,  stderr=tout, stdout=tout) | 
|  | 874             tout.write("eph_test Ran %s got %d" % (" ".join(cll), p.returncode)) | 
| 75 | 875         tout.close() | 
|  | 876         return p.returncode | 
| 63 | 877 | 
|  | 878     def planemo_test(self, genoutputs=True): | 
|  | 879         """planemo is a requirement so is available for testing | 
|  | 880         and for generating test outputs if command or test overrides are supplied | 
|  | 881         test outputs are sent to repdir for display | 
| 67 | 882         planemo test --engine docker_galaxy --database_type docker_postgres --galaxy_root /galaxy-central pyrevpos/pyrevpos.xml | 
| 63 | 883         """ | 
|  | 884         xreal = "%s.xml" % self.tool_name | 
|  | 885         if os.path.exists(self.tlog): | 
|  | 886             tout = open(self.tlog, "a") | 
|  | 887         else: | 
|  | 888             tout = open(self.tlog, "w") | 
|  | 889         if genoutputs: | 
| 75 | 890             dummy, tfile = tempfile.mkstemp() | 
| 63 | 891             cll = [ | 
|  | 892                 "planemo", | 
|  | 893                 "test", | 
|  | 894                 "--galaxy_root", | 
|  | 895                 self.args.galaxy_root, | 
| 74 | 896                 "--update_test_data", | 
| 63 | 897                 xreal, | 
|  | 898             ] | 
|  | 899             p = subprocess.run( | 
| 75 | 900                 cll, | 
|  | 901                 shell=False, | 
|  | 902                 cwd=self.tooloutdir, | 
|  | 903                 stderr=dummy, | 
|  | 904                 stdout=dummy, | 
|  | 905             ) | 
| 63 | 906         else: | 
| 75 | 907             cll = [ | 
|  | 908                 "planemo", | 
|  | 909                 "test", | 
|  | 910                 "--galaxy_root", | 
| 74 | 911                 self.args.galaxy_root, | 
| 75 | 912                 xreal, | 
|  | 913             ] | 
| 63 | 914             p = subprocess.run( | 
| 75 | 915                 cll, shell=False, cwd=self.tooloutdir, stderr=tout, stdout=tout | 
|  | 916             ) | 
| 63 | 917         tout.close() | 
|  | 918         return p.returncode | 
|  | 919 | 
| 48 | 920     def writeShedyml(self): | 
| 75 | 921         """for planemo""" | 
| 49 | 922         yuser = self.args.user_email.split("@")[0] | 
|  | 923         yfname = os.path.join(self.tooloutdir, ".shed.yml") | 
|  | 924         yamlf = open(yfname, "w") | 
|  | 925         odict = { | 
|  | 926             "name": self.tool_name, | 
|  | 927             "owner": yuser, | 
|  | 928             "type": "unrestricted", | 
|  | 929             "description": self.args.tool_desc, | 
| 50 | 930             "synopsis": self.args.tool_desc, | 
|  | 931             "category": "TF Generated Tools", | 
| 49 | 932         } | 
| 48 | 933         yaml.dump(odict, yamlf, allow_unicode=True) | 
|  | 934         yamlf.close() | 
|  | 935 | 
| 50 | 936     def makeTool(self): | 
| 75 | 937         """write xmls and input samples into place""" | 
| 50 | 938         self.makeXML() | 
|  | 939         if self.args.script_path: | 
|  | 940             stname = os.path.join(self.tooloutdir, "%s" % (self.sfile)) | 
|  | 941             if not os.path.exists(stname): | 
|  | 942                 shutil.copyfile(self.sfile, stname) | 
|  | 943         xreal = "%s.xml" % self.tool_name | 
|  | 944         xout = os.path.join(self.tooloutdir, xreal) | 
|  | 945         shutil.copyfile(xreal, xout) | 
|  | 946         for p in self.infiles: | 
|  | 947             pth = p[IPATHPOS] | 
|  | 948             dest = os.path.join(self.testdir, "%s_sample" % p[ICLPOS]) | 
|  | 949             shutil.copyfile(pth, dest) | 
|  | 950             dest = os.path.join(self.repdir, "%s.%s" % (p[ICLPOS], p[IFMTPOS])) | 
|  | 951             shutil.copyfile(pth, dest) | 
| 49 | 952 | 
| 50 | 953     def makeToolTar(self): | 
| 75 | 954         """move outputs into test-data and prepare the tarball""" | 
| 66 | 955         excludeme = "tool_test_output" | 
| 75 | 956 | 
| 66 | 957         def exclude_function(tarinfo): | 
| 75 | 958             filename = tarinfo.name | 
|  | 959             return ( | 
|  | 960                 None | 
|  | 961                 if filename.startswith(excludeme) or \ | 
|  | 962                 os.path.splitext(filename)[1].startswith(excludeme) | 
|  | 963                 else tarinfo | 
|  | 964             ) | 
| 66 | 965 | 
| 50 | 966         for p in self.outfiles: | 
|  | 967             src = p[ONAMEPOS] | 
|  | 968             if os.path.isfile(src): | 
|  | 969                 dest = os.path.join(self.testdir, "%s_sample" % src) | 
|  | 970                 shutil.copyfile(src, dest) | 
|  | 971                 dest = os.path.join(self.repdir, "%s.%s" % (src, p[OFMTPOS])) | 
|  | 972                 shutil.copyfile(src, dest) | 
|  | 973             else: | 
|  | 974                 print( | 
|  | 975                     "### problem - output file %s not found in tooloutdir %s" | 
|  | 976                     % (src, self.tooloutdir) | 
|  | 977                 ) | 
|  | 978         tf = tarfile.open(self.newtarpath, "w:gz") | 
| 66 | 979         tf.add(name=self.tooloutdir, arcname=self.tool_name, filter=exclude_function) | 
| 50 | 980         tf.close() | 
|  | 981         shutil.copyfile(self.newtarpath, self.args.new_tool) | 
|  | 982 | 
|  | 983     def moveRunOutputs(self): | 
| 75 | 984         """need to move planemo or run outputs into toolfactory collection""" | 
| 50 | 985         with os.scandir(self.tooloutdir) as outs: | 
|  | 986             for entry in outs: | 
|  | 987                 if not entry.is_file() or entry.name.startswith("."): | 
|  | 988                     continue | 
|  | 989                 if "." in entry.name: | 
|  | 990                     nayme, ext = os.path.splitext(entry.name) | 
|  | 991                 else: | 
|  | 992                     ext = ".txt" | 
|  | 993                 ofn = "%s%s" % (entry.name.replace(".", "_"), ext) | 
|  | 994                 dest = os.path.join(self.repdir, ofn) | 
|  | 995                 src = os.path.join(self.tooloutdir, entry.name) | 
|  | 996                 shutil.copyfile(src, dest) | 
|  | 997 | 
| 49 | 998 | 
| 76 | 999 | 
| 48 | 1000 def main(): | 
|  | 1001     """ | 
|  | 1002     This is a Galaxy wrapper. It expects to be called by a special purpose tool.xml as: | 
| 49 | 1003     <command interpreter="python">rgBaseScriptWrapper.py --script_path "$scriptPath" | 
|  | 1004     --tool_name "foo" --interpreter "Rscript" | 
| 48 | 1005     </command> | 
|  | 1006     """ | 
|  | 1007     parser = argparse.ArgumentParser() | 
|  | 1008     a = parser.add_argument | 
| 49 | 1009     a("--script_path", default=None) | 
|  | 1010     a("--history_test", default=None) | 
|  | 1011     a("--cl_prefix", default=None) | 
|  | 1012     a("--sysexe", default=None) | 
|  | 1013     a("--packages", default=None) | 
| 76 | 1014     a("--tool_name", default="newtool") | 
| 72 | 1015     a("--tool_dir", default=None) | 
| 48 | 1016     a("--input_files", default=[], action="append") | 
|  | 1017     a("--output_files", default=[], action="append") | 
|  | 1018     a("--user_email", default="Unknown") | 
|  | 1019     a("--bad_user", default=None) | 
| 49 | 1020     a("--make_Tool", default="runonly") | 
| 48 | 1021     a("--help_text", default=None) | 
|  | 1022     a("--tool_desc", default=None) | 
|  | 1023     a("--tool_version", default=None) | 
|  | 1024     a("--citations", default=None) | 
| 49 | 1025     a("--command_override", default=None) | 
|  | 1026     a("--test_override", default=None) | 
| 48 | 1027     a("--additional_parameters", action="append", default=[]) | 
|  | 1028     a("--edit_additional_parameters", action="store_true", default=False) | 
|  | 1029     a("--parampass", default="positional") | 
|  | 1030     a("--tfout", default="./tfout") | 
|  | 1031     a("--new_tool", default="new_tool") | 
| 49 | 1032     a("--galaxy_url", default="http://localhost:8080") | 
| 75 | 1033     a( | 
| 76 | 1034         "--toolshed_url", default="http://localhost:9009") | 
|  | 1035     # make sure this is identical to tool_sheds_conf.xml  localhost != 127.0.0.1 so validation fails | 
| 63 | 1036     a("--toolshed_api_key", default="fakekey") | 
| 50 | 1037     a("--galaxy_api_key", default="fakekey") | 
|  | 1038     a("--galaxy_root", default="/galaxy-central") | 
| 48 | 1039     args = parser.parse_args() | 
|  | 1040     assert not args.bad_user, ( | 
|  | 1041         'UNAUTHORISED: %s is NOT authorized to use this tool until Galaxy admin adds %s to "admin_users" in the Galaxy configuration file' | 
|  | 1042         % (args.bad_user, args.bad_user) | 
|  | 1043     ) | 
| 49 | 1044     assert args.tool_name, "## Tool Factory expects a tool name - eg --tool_name=DESeq" | 
| 48 | 1045     assert ( | 
| 49 | 1046         args.sysexe or args.packages | 
| 48 | 1047     ), "## Tool Factory wrapper expects an interpreter or an executable package" | 
| 49 | 1048     args.input_files = [x.replace('"', "").replace("'", "") for x in args.input_files] | 
| 48 | 1049     # remove quotes we need to deal with spaces in CL params | 
|  | 1050     for i, x in enumerate(args.additional_parameters): | 
| 49 | 1051         args.additional_parameters[i] = args.additional_parameters[i].replace('"', "") | 
| 48 | 1052     r = ScriptRunner(args) | 
| 49 | 1053     r.writeShedyml() | 
|  | 1054     r.makeTool() | 
| 66 | 1055     if args.make_Tool == "generate": | 
|  | 1056         retcode = r.run() | 
|  | 1057         r.moveRunOutputs() | 
|  | 1058         r.makeToolTar() | 
|  | 1059     else: | 
|  | 1060         retcode = r.planemo_test(genoutputs=True)  # this fails :( - see PR | 
|  | 1061         r.moveRunOutputs() | 
|  | 1062         r.makeToolTar() | 
|  | 1063         retcode = r.planemo_test(genoutputs=False) | 
|  | 1064         r.moveRunOutputs() | 
| 76 | 1065         print(f"second planemo_test returned {retcode}") | 
| 66 | 1066         if args.make_Tool == "gentestinstall": | 
|  | 1067             r.eph_galaxy_load() | 
| 63 | 1068 | 
| 48 | 1069 | 
|  | 1070 if __name__ == "__main__": | 
|  | 1071     main() |