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