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