Mercurial > repos > shellac > sam_consensus_v3
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 ) |