comparison env/lib/python3.7/site-packages/planemo/tool_builder.py @ 0:26e78fe6e8c4 draft

"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author shellac
date Sat, 02 May 2020 07:14:21 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:26e78fe6e8c4
1 """This module contains :func:`build` to build tool descriptions.
2
3 This class is used by the `tool_init` command and can be used to build
4 Galaxy and CWL tool descriptions.
5 """
6
7 import os
8 import re
9 import shlex
10 import shutil
11 import subprocess
12 from collections import namedtuple
13
14 from planemo import io
15 from planemo import templates
16
17 REUSING_MACROS_MESSAGE = ("Macros file macros.xml already exists, assuming "
18 " it has relevant planemo-generated definitions.")
19 DEFAULT_CWL_VERSION = "v1.0"
20
21
22 TOOL_TEMPLATE = """<tool id="{{id}}" name="{{name}}" version="{{version}}" python_template_version="3.5">
23 {%- if description %}
24 <description>{{ description }}</description>
25 {%- endif %}
26 {%- if macros %}
27 <macros>
28 <import>macros.xml</import>
29 </macros>
30 <expand macro="requirements" />
31 {%- if version_command %}
32 <expand macro="version_command" />
33 {%- endif %}
34 {%- else %}
35 <requirements>
36 {%- for requirement in requirements %}
37 {{ requirement }}
38 {%- endfor %}
39 {%- for container in containers %}
40 {{ container }}
41 {%- endfor %}
42 </requirements>
43 {%- if version_command %}
44 <version_command>{{ version_command }}</version_command>
45 {%- endif %}
46 {%- endif %}
47 <command detect_errors="exit_code"><![CDATA[
48 {%- if command %}
49 {{ command }}
50 {%- else %}
51 TODO: Fill in command template.
52 {%- endif %}
53 ]]></command>
54 <inputs>
55 {%- for input in inputs %}
56 {{ input }}
57 {%- endfor %}
58 </inputs>
59 <outputs>
60 {%- for output in outputs %}
61 {{ output }}
62 {%- endfor %}
63 </outputs>
64 {%- if tests %}
65 <tests>
66 {%- for test in tests %}
67 <test>
68 {%- for param in test.params %}
69 <param name="{{ param[0]}}" value="{{ param[1] }}"/>
70 {%- endfor %}
71 {%- for output in test.outputs %}
72 <output name="{{ output[0] }}" file="{{ output[1] }}"/>
73 {%- endfor %}
74 </test>
75 {%- endfor %}
76 </tests>
77 {%- endif %}
78 <help><![CDATA[
79 {%- if help %}
80 {{ help }}
81 {%- else %}
82 TODO: Fill in help.
83 {%- endif %}
84 ]]></help>
85 {%- if macros %}
86 <expand macro="citations" />
87 {%- else %}
88 {%- if doi or bibtex_citations %}
89 <citations>
90 {%- for single_doi in doi %}
91 <citation type="doi">{{ single_doi }}</citation>
92 {%- endfor %}
93 {%- for bibtex_citation in bibtex_citations %}
94 <citation type="bibtex">{{ bibtex_citation }}</citation>
95 {%- endfor %}
96 </citations>
97 {%- endif %}
98 {%- endif %}
99 </tool>
100 """
101
102 MACROS_TEMPLATE = """<macros>
103 <xml name="requirements">
104 <requirements>
105 {%- for requirement in requirements %}
106 {{ requirement }}
107 {%- endfor %}
108 <yield/>
109 {%- for container in containers %}
110 {{ container }}
111 {%- endfor %}
112 </requirements>
113 </xml>
114 <xml name="citations">
115 <citations>
116 {%- for single_doi in doi %}
117 <citation type="doi">{{ single_doi }}</citation>
118 {%- endfor %}
119 {%- for bibtex_citation in bibtex_citations %}
120 <citation type="bibtex">{{ bibtex_citation }}</citation>
121 {%- endfor %}
122 <yield />
123 </citations>
124 </xml>
125 {%- if version_command %}
126 <xml name="version_command">
127 <version_command>{{ version_command }}</version_command>
128 </xml>
129 {%- endif %}
130 </macros>
131 """
132
133 CWL_TEMPLATE = """#!/usr/bin/env cwl-runner
134 cwlVersion: '{{cwl_version}}'
135 class: CommandLineTool
136 id: "{{id}}"
137 label: "{{label}}"
138 {%- if containers or requirements %}
139 hints:
140 {%- for container in containers %}
141 DockerRequirement:
142 dockerPull: {{ container.image_id }}
143 {%- endfor %}
144 {%- if requirements %}
145 SoftwareRequirement:
146 packages:
147 {%- for requirement in requirements %}
148 - package: {{ requirement.name }}
149 {%- if requirement.version %}
150 version:
151 - "{{ requirement.version }}"
152 {%- else %}
153 version: []
154 {%- endif %}
155 {%- endfor %}
156 {%- endif %}
157 {%- endif %}
158 {%- if inputs or outputs %}
159 inputs:
160 {%- for input in inputs %}
161 {{ input.id }}:
162 type: {{ input.type }}
163 doc: |
164 TODO
165 inputBinding:
166 position: {{ input.position }}
167 {%- if input.prefix %}
168 prefix: "{{input.prefix.prefix}}"
169 {%- if not input.prefix.separated %}
170 separate: false
171 {%- endif %}
172 {%- endif %}
173 {%- endfor %}
174 {%- for output in outputs %}
175 {%- if output.require_filename %}
176 {{ output.id }}:
177 type: string
178 doc: |
179 Filename for output {{ output.id }}
180 inputBinding:
181 position: {{ output.position }}
182 {%- if output.prefix %}
183 prefix: "{{output.prefix.prefix}}"
184 {%- if not output.prefix.separated %}
185 separate: false
186 {%- endif %}
187 {%- endif %}
188 {%- endif %}
189 {%- endfor %}
190 {%- else %}
191 inputs: [] # TODO
192 {%- endif %}
193 {%- if outputs %}
194 outputs:
195 {%- for output in outputs %}
196 {{ output.id }}:
197 type: File
198 outputBinding:
199 glob: {{ output.glob }}
200 {%- endfor %}
201 {%- else %}
202 outputs: [] # TODO
203 {%- endif %}
204 {%- if base_command %}
205 baseCommand:
206 {%- for base_command_part in base_command %}
207 - "{{ base_command_part}}"
208 {%- endfor %}
209 {%- else %}
210 baseCommand: []
211 {%- endif %}
212 {%- if arguments %}
213 arguments:
214 {%- for argument in arguments %}
215 - valueFrom: "{{ argument.value }}"
216 position: {{ argument.position }}
217 {%- if argument.prefix %}
218 prefix: "{{argument.prefix.prefix}}"
219 {%- if not argument.prefix.separated %}
220 separate: false
221 {%- endif %}
222 {%- endif %}
223 {%- endfor %}
224 {%- else %}
225 arguments: []
226 {%- endif %}
227 {%- if stdout %}
228 stdout: {{ stdout }}
229 {%- endif %}
230 doc: |
231 {%- if help %}
232 {{ help|indent(2) }}
233 {%- else %}
234 TODO: Fill in description.
235 {%- endif %}
236 """
237
238 CWL_TEST_TEMPLATE = """
239 - doc: test generated from example command
240 job: {{ job_filename }}
241 {%- if outputs %}
242 outputs:
243 {%- for output in outputs %}
244 {{ output.id }}:
245 path: test-data/{{ output.example_value }}
246 {%- endfor %}
247 {%- else %}
248 outputs: TODO
249 {%- endif %}
250 """
251
252 CWL_JOB_TEMPLATE = """
253 {%- if inputs %}
254 {%- for input in inputs %}
255 {%- if input.type == "File" %}
256 {{ input.id }}:
257 class: File
258 path: test-data/{{ input.example_value }}
259 {%- else %}
260 {{ input.id }}: {{ input.example_value }}
261 {%- endif %}
262 {%- endfor %}
263 {%- else %}
264 # TODO: Specify job input.
265 {}
266 {%- endif %}
267 """
268
269
270 def build(**kwds):
271 """Build up a :func:`ToolDescription` from supplid arguments."""
272 if kwds.get("cwl"):
273 builder = _build_cwl
274 else:
275 builder = _build_galaxy
276 return builder(**kwds)
277
278
279 def _build_cwl(**kwds):
280 _handle_help(kwds)
281 _handle_requirements(kwds)
282 assert len(kwds["containers"]) <= 1, kwds
283 command_io = CommandIO(**kwds)
284 render_kwds = {
285 "cwl_version": DEFAULT_CWL_VERSION,
286 "help": kwds.get("help", ""),
287 "containers": kwds.get("containers", []),
288 "requirements": kwds.get("requirements", []),
289 "id": kwds.get("id"),
290 "label": kwds.get("name"),
291 }
292 render_kwds.update(command_io.cwl_properties())
293
294 contents = _render(render_kwds, template_str=CWL_TEMPLATE)
295 tool_files = []
296 test_files = []
297 if kwds["test_case"]:
298 sep = "-" if "-" in kwds.get("id") else "_"
299 tests_path = "%s%stests.yml" % (kwds.get("id"), sep)
300 job_path = "%s%sjob.yml" % (kwds.get("id"), sep)
301 render_kwds["job_filename"] = job_path
302 test_contents = _render(render_kwds, template_str=CWL_TEST_TEMPLATE)
303 job_contents = _render(render_kwds, template_str=CWL_JOB_TEMPLATE)
304 tool_files.append(ToolFile(tests_path, test_contents, "test"))
305 tool_files.append(ToolFile(job_path, job_contents, "job"))
306 for cwl_input in render_kwds["inputs"] or []:
307 if cwl_input.type == "File" and cwl_input.example_value:
308 test_files.append(cwl_input.example_value)
309
310 for cwl_output in render_kwds["outputs"] or []:
311 if cwl_output.example_value:
312 test_files.append(cwl_output.example_value)
313
314 return ToolDescription(
315 contents,
316 tool_files=tool_files,
317 test_files=test_files
318 )
319
320
321 def _build_galaxy(**kwds):
322 # Test case to build up from supplied inputs and outputs, ultimately
323 # ignored unless kwds["test_case"] is truthy.
324
325 _handle_help(kwds)
326
327 # process raw cite urls
328 cite_urls = kwds.get("cite_url", [])
329 del kwds["cite_url"]
330 citations = list(map(UrlCitation, cite_urls))
331 kwds["bibtex_citations"] = citations
332
333 # handle requirements and containers
334 _handle_requirements(kwds)
335
336 command_io = CommandIO(**kwds)
337 kwds["inputs"] = command_io.inputs
338 kwds["outputs"] = command_io.outputs
339 kwds["command"] = command_io.cheetah_template
340
341 test_case = command_io.test_case()
342
343 # finally wrap up tests
344 tests, test_files = _handle_tests(kwds, test_case)
345 kwds["tests"] = tests
346
347 # Render tool content from template.
348 contents = _render(kwds)
349
350 tool_files = []
351 append_macro_file(tool_files, kwds)
352
353 return ToolDescription(
354 contents,
355 tool_files=tool_files,
356 test_files=test_files
357 )
358
359
360 def append_macro_file(tool_files, kwds):
361 macro_contents = None
362 if kwds["macros"]:
363 macro_contents = _render(kwds, MACROS_TEMPLATE)
364
365 macros_file = "macros.xml"
366 if not os.path.exists(macros_file):
367 tool_files.append(ToolFile(macros_file, macro_contents, "macros"))
368
369 io.info(REUSING_MACROS_MESSAGE)
370
371
372 class CommandIO(object):
373
374 def __init__(self, **kwds):
375 command = _find_command(kwds)
376 cheetah_template = command
377
378 # process raw inputs
379 inputs = kwds.pop("input", [])
380 inputs = list(map(Input, inputs or []))
381
382 # alternatively process example inputs
383 example_inputs = kwds.pop("example_input", [])
384 for i, input_file in enumerate(example_inputs or []):
385 name = "input%d" % (i + 1)
386 inputs.append(Input(input_file, name=name, example=True))
387 cheetah_template = _replace_file_in_command(cheetah_template, input_file, name)
388
389 # handle raw outputs (from_work_dir ones) as well as named_outputs
390 outputs = kwds.pop("output", [])
391 outputs = list(map(Output, outputs or []))
392
393 named_outputs = kwds.pop("named_output", [])
394 for named_output in (named_outputs or []):
395 outputs.append(Output(name=named_output, example=False))
396
397 # handle example outputs
398 example_outputs = kwds.pop("example_output", [])
399 for i, output_file in enumerate(example_outputs or []):
400 name = "output%d" % (i + 1)
401 from_path = output_file
402 use_from_path = True
403 if output_file in cheetah_template:
404 # Actually found the file in the command, assume it can
405 # be specified directly and skip from_work_dir.
406 use_from_path = False
407 output = Output(name=name, from_path=from_path,
408 use_from_path=use_from_path, example=True)
409 outputs.append(output)
410 cheetah_template = _replace_file_in_command(cheetah_template, output_file, output.name)
411
412 self.inputs = inputs
413 self.outputs = outputs
414 self.command = command
415 self.cheetah_template = cheetah_template
416
417 def example_input_names(self):
418 for input in self.inputs:
419 if input.example:
420 yield input.input_description
421
422 def example_output_names(self):
423 for output in self.outputs:
424 if output.example:
425 yield output.example_path
426
427 def cwl_lex_list(self):
428 if not self.command:
429 return []
430
431 command_parts = shlex.split(self.command)
432 parse_list = []
433
434 input_count = 0
435 output_count = 0
436
437 index = 0
438
439 prefixed_parts = []
440 while index < len(command_parts):
441 value = command_parts[index]
442 eq_split = value.split("=")
443
444 prefix = None
445 if not _looks_like_start_of_prefix(index, command_parts):
446 index += 1
447 elif len(eq_split) == 2:
448 prefix = Prefix(eq_split[0] + "=", False)
449 value = eq_split[1]
450 index += 1
451 else:
452 prefix = Prefix(value, True)
453 value = command_parts[index + 1]
454 index += 2
455 prefixed_parts.append((prefix, value))
456
457 for position, (prefix, value) in enumerate(prefixed_parts):
458 if value in self.example_input_names():
459 input_count += 1
460 input = CwlInput(
461 "input%d" % input_count,
462 position,
463 prefix,
464 value,
465 )
466 parse_list.append(input)
467 elif value in self.example_output_names():
468 output_count += 1
469 output = CwlOutput(
470 "output%d" % output_count,
471 position,
472 prefix,
473 value,
474 )
475 parse_list.append(output)
476 elif prefix:
477 param_id = prefix.prefix.lower().rstrip("=")
478 type_ = param_type(value)
479 input = CwlInput(
480 param_id,
481 position,
482 prefix,
483 value,
484 type_=type_,
485 )
486 parse_list.append(input)
487 else:
488 part = CwlCommandPart(value, position, prefix)
489 parse_list.append(part)
490 return parse_list
491
492 def cwl_properties(self):
493 base_command = []
494 arguments = []
495 inputs = []
496 outputs = []
497
498 lex_list = self.cwl_lex_list()
499
500 index = 0
501 while index < len(lex_list):
502 token = lex_list[index]
503 if isinstance(token, CwlCommandPart):
504 base_command.append(token.value)
505 else:
506 break
507 index += 1
508
509 while index < len(lex_list):
510 token = lex_list[index]
511 if token.is_token(">"):
512 break
513 token.position = index - len(base_command) + 1
514 if isinstance(token, CwlCommandPart):
515 arguments.append(token)
516 elif isinstance(token, CwlInput):
517 inputs.append(token)
518 elif isinstance(token, CwlOutput):
519 token.glob = "$(inputs.%s)" % token.id
520 outputs.append(token)
521
522 index += 1
523
524 stdout = None
525 if index < len(lex_list):
526 token = lex_list[index]
527 if token.is_token(">") and (index + 1) < len(lex_list):
528 output_token = lex_list[index + 1]
529 if not isinstance(output_token, CwlOutput):
530 output_token = CwlOutput("std_out", None)
531
532 output_token.glob = "out"
533 output_token.require_filename = False
534 outputs.append(output_token)
535 stdout = "out"
536 index += 2
537 else:
538 io.warn("Example command too complex, you will need to build it up manually.")
539
540 return {
541 "inputs": inputs,
542 "outputs": outputs,
543 "arguments": arguments,
544 "base_command": base_command,
545 "stdout": stdout,
546 }
547
548 def test_case(self):
549 test_case = TestCase()
550 for input in self.inputs:
551 if input.example:
552 test_case.params.append((input.name, input.input_description))
553
554 for output in self.outputs:
555 if output.example:
556 test_case.outputs.append((output.name, output.example_path))
557
558 return test_case
559
560
561 def _looks_like_start_of_prefix(index, parts):
562 value = parts[index]
563 if len(value.split("=")) == 2:
564 return True
565 if index + 1 == len(parts):
566 return False
567 next_value = parts[index + 1]
568 next_value_is_not_start = (len(value.split("=")) != 2) and next_value[0] not in ["-", ">", "<", "|"]
569 return value.startswith("-") and next_value_is_not_start
570
571
572 Prefix = namedtuple("Prefix", ["prefix", "separated"])
573
574
575 class CwlCommandPart(object):
576
577 def __init__(self, value, position, prefix):
578 self.value = value
579 self.position = position
580 self.prefix = prefix
581
582 def is_token(self, value):
583 return self.value == value
584
585
586 class CwlInput(object):
587
588 def __init__(self, id, position, prefix, example_value, type_="File"):
589 self.id = id
590 self.position = position
591 self.prefix = prefix
592 self.example_value = example_value
593 self.type = type_
594
595 def is_token(self, value):
596 return False
597
598
599 class CwlOutput(object):
600
601 def __init__(self, id, position, prefix, example_value):
602 self.id = id
603 self.position = position
604 self.prefix = prefix
605 self.glob = None
606 self.example_value = example_value
607 self.require_filename = True
608
609 def is_token(self, value):
610 return False
611
612
613 def _render(kwds, template_str=TOOL_TEMPLATE):
614 """ Apply supplied template variables to TOOL_TEMPLATE to generate
615 the final tool.
616 """
617 return templates.render(template_str, **kwds)
618
619
620 def _replace_file_in_command(command, specified_file, name):
621 """ Replace example file with cheetah variable name in supplied command
622 or command template. Be sure to single quote the name.
623 """
624 # TODO: check if the supplied variant was single quoted already.
625 if '"%s"' % specified_file in command:
626 # Sample command already wrapped filename in double quotes
627 command = command.replace('"%s"' % specified_file, "'$%s'" % name)
628 elif (" %s " % specified_file) in (" " + command + " "):
629 # In case of spaces, best to wrap filename in double quotes
630 command = command.replace(specified_file, "'$%s'" % name)
631 else:
632 command = command.replace(specified_file, '$%s' % name)
633 return command
634
635
636 def _handle_help(kwds):
637 """ Convert supplied help parameters into a help variable for template.
638 If help_text is supplied, use as is. If help is specified from a command,
639 run the command and use that help text.
640 """
641 help_text = kwds.get("help_text")
642 if not help_text:
643 help_from_command = kwds.get("help_from_command")
644 if help_from_command:
645 p = subprocess.Popen(
646 help_from_command,
647 shell=True,
648 stdout=subprocess.PIPE,
649 stderr=subprocess.STDOUT,
650 universal_newlines=True
651 )
652 help_text = p.communicate()[0]
653
654 del kwds["help_text"]
655 del kwds["help_from_command"]
656
657 kwds["help"] = help_text
658
659
660 def _handle_tests(kwds, test_case):
661 """ Given state built up from handling rest of arguments (test_case) and
662 supplied kwds - build tests for template and corresponding test files.
663 """
664 test_files = []
665 if kwds["test_case"]:
666 tests = [test_case]
667 test_files.extend(map(lambda x: x[1], test_case.params))
668 test_files.extend(map(lambda x: x[1], test_case.outputs))
669 else:
670 tests = []
671 return tests, test_files
672
673
674 def _handle_requirements(kwds):
675 """ Convert requirements and containers specified from the command-line
676 into abstract format for consumption by the template.
677 """
678 requirements = kwds["requirement"]
679 del kwds["requirement"]
680 requirements = list(map(Requirement, requirements or []))
681
682 container = kwds["container"]
683 del kwds["container"]
684 containers = list(map(Container, container or []))
685
686 kwds["requirements"] = requirements
687 kwds["containers"] = containers
688
689
690 def _find_command(kwds):
691 """Find base command from supplied arguments or just return None.
692
693 If no such command was supplied (template will just replace this
694 with a TODO item).
695 """
696 command = kwds.get("command")
697 if not command:
698 command = kwds.get("example_command", None)
699 if command:
700 del kwds["example_command"]
701 return command
702
703
704 class UrlCitation(object):
705
706 def __init__(self, url):
707 self.url = url
708
709 def __str__(self):
710 if "github.com" in self.url:
711 return self._github_str()
712 else:
713 return self._url_str()
714
715 def _github_str(self):
716 url = self.url
717 title = url.split("/")[-1]
718 return '''
719 @misc{github%s,
720 author = {LastTODO, FirstTODO},
721 year = {TODO},
722 title = {%s},
723 publisher = {GitHub},
724 journal = {GitHub repository},
725 url = {%s},
726 }''' % (title, title, url)
727
728 def _url_str(self):
729 url = self.url
730 return '''
731 @misc{renameTODO,
732 author = {LastTODO, FirstTODO},
733 year = {TODO},
734 title = {TODO},
735 url = {%s},
736 }''' % (url)
737
738
739 class ToolDescription(object):
740 """An description of the tool and related files to create."""
741
742 def __init__(self, contents, tool_files=None, test_files=[]):
743 self.contents = contents
744 self.tool_files = tool_files or []
745 self.test_files = test_files
746
747
748 class ToolFile(object):
749
750 def __init__(self, filename, contents, description):
751 self.filename = filename
752 self.contents = contents
753 self.description = description
754
755
756 class Input(object):
757
758 def __init__(self, input_description, name=None, example=False):
759 parts = input_description.split(".")
760 name = name or parts[0]
761 if len(parts) > 0:
762 datatype = ".".join(parts[1:])
763 else:
764 datatype = "data"
765
766 self.input_description = input_description
767 self.example = example
768 self.name = name
769 self.datatype = datatype
770
771 def __str__(self):
772 template = '<param type="data" name="{0}" format="{1}" />'
773 self.datatype = self.datatype.split(".")[-1]
774 return template.format(self.name, self.datatype)
775
776
777 class Output(object):
778
779 def __init__(self, from_path=None, name=None, use_from_path=False, example=False):
780 if from_path:
781 parts = from_path.split(".")
782 name = name or parts[0]
783 if len(parts) > 1:
784 datatype = ".".join(parts[1:])
785 else:
786 datatype = "data"
787 else:
788 name = name
789 datatype = "data"
790
791 self.name = name
792 self.datatype = datatype
793 if use_from_path:
794 self.from_path = from_path
795 else:
796 self.from_path = None
797 self.example = example
798 if example:
799 self.example_path = from_path
800
801 def __str__(self):
802 if self.from_path:
803 return self._from_path_str()
804 else:
805 return self._named_str()
806
807 def _from_path_str(self):
808 template = '<data name="{0}" format="{1}" from_work_dir="{2}" />'
809 return template.format(self.name, self.datatype, self.from_path)
810
811 def _named_str(self):
812 template = '<data name="{0}" format="{1}" />'
813 return template.format(self.name, self.datatype)
814
815
816 class Requirement(object):
817
818 def __init__(self, requirement):
819 parts = requirement.split("@", 1)
820 if len(parts) > 1:
821 name = parts[0]
822 version = "@".join(parts[1:])
823 else:
824 name = parts[0]
825 version = None
826 self.name = name
827 self.version = version
828
829 def __str__(self):
830 base = '<requirement type="package"{0}>{1}</requirement>'
831 if self.version is not None:
832 attrs = ' version="{0}"'.format(self.version)
833 else:
834 attrs = ''
835 return base.format(attrs, self.name)
836
837
838 def param_type(value):
839 if re.match(r"^\d+$", value):
840 return "int"
841 elif re.match(r"^\d+?\.\d+?$", value):
842 return "float"
843 else:
844 return "string"
845
846
847 class Container(object):
848
849 def __init__(self, image_id):
850 self.type = "docker"
851 self.image_id = image_id
852
853 def __str__(self):
854 template = '<container type="{0}">{1}</container>'
855 return template.format(self.type, self.image_id)
856
857
858 class TestCase(object):
859
860 def __init__(self):
861 self.params = []
862 self.outputs = []
863
864
865 def write_tool_description(ctx, tool_description, **kwds):
866 """Write a tool description to the file system guided by supplied CLI kwds."""
867 tool_id = kwds.get("id")
868 output = kwds.get("tool")
869 if not output:
870 extension = "cwl" if kwds.get("cwl") else "xml"
871 output = "%s.%s" % (tool_id, extension)
872 if not io.can_write_to_path(output, **kwds):
873 ctx.exit(1)
874
875 io.write_file(output, tool_description.contents)
876 io.info("Tool written to %s" % output)
877 for tool_file in tool_description.tool_files:
878 if tool_file.contents is None:
879 continue
880
881 path = tool_file.filename
882 if not io.can_write_to_path(path, **kwds):
883 ctx.exit(1)
884 io.write_file(path, tool_file.contents)
885 io.info("Tool %s written to %s" % (tool_file.description, path))
886
887 macros = kwds["macros"]
888 macros_file = "macros.xml"
889 if macros and not os.path.exists(macros_file):
890 io.write_file(macros_file, tool_description.macro_contents)
891 elif macros:
892 io.info(REUSING_MACROS_MESSAGE)
893 if tool_description.test_files:
894 if not os.path.exists("test-data"):
895 io.info("No test-data directory, creating one.")
896 os.makedirs('test-data')
897 for test_file in tool_description.test_files:
898 io.info("Copying test-file %s" % test_file)
899 try:
900 shutil.copy(test_file, 'test-data')
901 except Exception as e:
902 io.info("Copy of %s failed: %s" % (test_file, e))
903
904
905 __all__ = (
906 "build",
907 "ToolDescription",
908 "write_tool_description",
909 )