comparison env/lib/python3.9/site-packages/planemo/tool_builder.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
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 """Render template variables to generate the final tool."""
615 return templates.render(template_str, **kwds)
616
617
618 def _replace_file_in_command(command, specified_file, name):
619 """Replace example file with cheetah variable name.
620
621 Be sure to single quote the name.
622 """
623 # TODO: check if the supplied variant was single quoted already.
624 if '"%s"' % specified_file in command:
625 # Sample command already wrapped filename in double quotes
626 command = command.replace('"%s"' % specified_file, "'$%s'" % name)
627 elif (" %s " % specified_file) in (" " + command + " "):
628 # In case of spaces, best to wrap filename in double quotes
629 command = command.replace(specified_file, "'$%s'" % name)
630 else:
631 command = command.replace(specified_file, '$%s' % name)
632 return command
633
634
635 def _handle_help(kwds):
636 """Convert supplied help parameters into a help variable for template.
637
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 """Build tool test abstractions.
662
663 Given state built up from handling rest of arguments (test_case) and
664 supplied kwds - build tests for template and corresponding test files.
665 """
666 test_files = []
667 if kwds["test_case"]:
668 tests = [test_case]
669 test_files.extend(map(lambda x: x[1], test_case.params))
670 test_files.extend(map(lambda x: x[1], test_case.outputs))
671 else:
672 tests = []
673 return tests, test_files
674
675
676 def _handle_requirements(kwds):
677 """Build tool requirement abstractions.
678
679 Convert requirements and containers specified from the command-line
680 into abstract format for consumption by the template.
681 """
682 requirements = kwds["requirement"]
683 del kwds["requirement"]
684 requirements = list(map(Requirement, requirements or []))
685
686 container = kwds["container"]
687 del kwds["container"]
688 containers = list(map(Container, container or []))
689
690 kwds["requirements"] = requirements
691 kwds["containers"] = containers
692
693
694 def _find_command(kwds):
695 """Find base command from supplied arguments or just return `None`.
696
697 If no such command was supplied (template will just replace this
698 with a TODO item).
699 """
700 command = kwds.get("command")
701 if not command:
702 command = kwds.get("example_command", None)
703 if command:
704 del kwds["example_command"]
705 return command
706
707
708 class UrlCitation(object):
709 """Describe citation for tool."""
710
711 def __init__(self, url):
712 self.url = url
713
714 def __str__(self):
715 if "github.com" in self.url:
716 return self._github_str()
717 else:
718 return self._url_str()
719
720 def _github_str(self):
721 url = self.url
722 title = url.split("/")[-1]
723 return '''
724 @misc{github%s,
725 author = {LastTODO, FirstTODO},
726 year = {TODO},
727 title = {%s},
728 publisher = {GitHub},
729 journal = {GitHub repository},
730 url = {%s},
731 }''' % (title, title, url)
732
733 def _url_str(self):
734 url = self.url
735 return '''
736 @misc{renameTODO,
737 author = {LastTODO, FirstTODO},
738 year = {TODO},
739 title = {TODO},
740 url = {%s},
741 }''' % (url)
742
743
744 class ToolDescription(object):
745 """An description of the tool and related files to create."""
746
747 def __init__(self, contents, tool_files=None, test_files=[]):
748 self.contents = contents
749 self.tool_files = tool_files or []
750 self.test_files = test_files
751
752
753 class ToolFile(object):
754
755 def __init__(self, filename, contents, description):
756 self.filename = filename
757 self.contents = contents
758 self.description = description
759
760
761 class Input(object):
762
763 def __init__(self, input_description, name=None, example=False):
764 parts = input_description.split(".")
765 name = name or parts[0]
766 if len(parts) > 0:
767 datatype = ".".join(parts[1:])
768 else:
769 datatype = "data"
770
771 self.input_description = input_description
772 self.example = example
773 self.name = name
774 self.datatype = datatype
775
776 def __str__(self):
777 template = '<param type="data" name="{0}" format="{1}" />'
778 self.datatype = self.datatype.split(".")[-1]
779 return template.format(self.name, self.datatype)
780
781
782 class Output(object):
783
784 def __init__(self, from_path=None, name=None, use_from_path=False, example=False):
785 if from_path:
786 parts = from_path.split(".")
787 name = name or parts[0]
788 if len(parts) > 1:
789 datatype = ".".join(parts[1:])
790 else:
791 datatype = "data"
792 else:
793 name = name
794 datatype = "data"
795
796 self.name = name
797 self.datatype = datatype
798 if use_from_path:
799 self.from_path = from_path
800 else:
801 self.from_path = None
802 self.example = example
803 if example:
804 self.example_path = from_path
805
806 def __str__(self):
807 if self.from_path:
808 return self._from_path_str()
809 else:
810 return self._named_str()
811
812 def _from_path_str(self):
813 template = '<data name="{0}" format="{1}" from_work_dir="{2}" />'
814 return template.format(self.name, self.datatype, self.from_path)
815
816 def _named_str(self):
817 template = '<data name="{0}" format="{1}" />'
818 return template.format(self.name, self.datatype)
819
820
821 class Requirement(object):
822
823 def __init__(self, requirement):
824 parts = requirement.split("@", 1)
825 if len(parts) > 1:
826 name = parts[0]
827 version = "@".join(parts[1:])
828 else:
829 name = parts[0]
830 version = None
831 self.name = name
832 self.version = version
833
834 def __str__(self):
835 base = '<requirement type="package"{0}>{1}</requirement>'
836 if self.version is not None:
837 attrs = ' version="{0}"'.format(self.version)
838 else:
839 attrs = ''
840 return base.format(attrs, self.name)
841
842
843 def param_type(value):
844 if re.match(r"^\d+$", value):
845 return "int"
846 elif re.match(r"^\d+?\.\d+?$", value):
847 return "float"
848 else:
849 return "string"
850
851
852 class Container(object):
853
854 def __init__(self, image_id):
855 self.type = "docker"
856 self.image_id = image_id
857
858 def __str__(self):
859 template = '<container type="{0}">{1}</container>'
860 return template.format(self.type, self.image_id)
861
862
863 class TestCase(object):
864
865 def __init__(self):
866 self.params = []
867 self.outputs = []
868
869
870 def write_tool_description(ctx, tool_description, **kwds):
871 """Write a tool description to the file system guided by supplied CLI kwds."""
872 tool_id = kwds.get("id")
873 output = kwds.get("tool")
874 if not output:
875 extension = "cwl" if kwds.get("cwl") else "xml"
876 output = "%s.%s" % (tool_id, extension)
877 if not io.can_write_to_path(output, **kwds):
878 ctx.exit(1)
879
880 io.write_file(output, tool_description.contents)
881 io.info("Tool written to %s" % output)
882 for tool_file in tool_description.tool_files:
883 if tool_file.contents is None:
884 continue
885
886 path = tool_file.filename
887 if not io.can_write_to_path(path, **kwds):
888 ctx.exit(1)
889 io.write_file(path, tool_file.contents)
890 io.info("Tool %s written to %s" % (tool_file.description, path))
891
892 macros = kwds["macros"]
893 macros_file = "macros.xml"
894 if macros and not os.path.exists(macros_file):
895 io.write_file(macros_file, tool_description.macro_contents)
896 elif macros:
897 io.info(REUSING_MACROS_MESSAGE)
898 if tool_description.test_files:
899 if not os.path.exists("test-data"):
900 io.info("No test-data directory, creating one.")
901 os.makedirs('test-data')
902 for test_file in tool_description.test_files:
903 io.info("Copying test-file %s" % test_file)
904 try:
905 shutil.copy(test_file, 'test-data')
906 except Exception as e:
907 io.info("Copy of %s failed: %s" % (test_file, e))
908
909
910 __all__ = (
911 "build",
912 "ToolDescription",
913 "write_tool_description",
914 )