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