Mercurial > repos > bcclaywell > argo_navis
comparison venv/lib/python2.7/site-packages/planemo/tool_builder.py @ 0:d67268158946 draft
planemo upload commit a3f181f5f126803c654b3a66dd4e83a48f7e203b
author | bcclaywell |
---|---|
date | Mon, 12 Oct 2015 17:43:33 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:d67268158946 |
---|---|
1 import subprocess | |
2 from planemo import templates | |
3 | |
4 | |
5 TOOL_TEMPLATE = """<tool id="{{id}}" name="{{name}}" version="{{version}}"> | |
6 {%- if description %} | |
7 <description>{{ description }}</description> | |
8 {% endif %} | |
9 {%- if macros %} | |
10 <macros> | |
11 <import>macros.xml</import> | |
12 </macros> | |
13 <expand macro="requirements" /> | |
14 <expand macro="stdio" /> | |
15 {%- if version_command %} | |
16 <expand macro="version_command" /> | |
17 {% endif %} | |
18 {%- else %} | |
19 <requirements> | |
20 {%- for requirement in requirements %} | |
21 {{ requirement }} | |
22 {%- endfor %} | |
23 {%- for container in containers %} | |
24 {{ container }} | |
25 {%- endfor %} | |
26 </requirements> | |
27 <stdio> | |
28 <exit_code range="1:" /> | |
29 </stdio> | |
30 {%- if version_command %} | |
31 <version_command>{{ version_command }}</version_command> | |
32 {%- endif %} | |
33 {% endif %} | |
34 <command><![CDATA[ | |
35 {%- if command %} | |
36 {{ command }} | |
37 {%- else %} | |
38 TODO: Fill in command template. | |
39 {%- endif %} | |
40 ]]></command> | |
41 <inputs> | |
42 {%- for input in inputs %} | |
43 {{ input }} | |
44 {%- endfor %} | |
45 </inputs> | |
46 <outputs> | |
47 {%- for output in outputs %} | |
48 {{ output }} | |
49 {%- endfor %} | |
50 </outputs> | |
51 {%- if tests %} | |
52 <tests> | |
53 {%- for test in tests %} | |
54 <test> | |
55 {%- for param in test.params %} | |
56 <param name="{{ param[0]}}" value="{{ param[1] }}"/> | |
57 {%- endfor %} | |
58 {%- for output in test.outputs %} | |
59 <output name="{{ output[0] }}" file="{{ output[1] }}"/> | |
60 {%- endfor %} | |
61 </test> | |
62 {%- endfor %} | |
63 </tests> | |
64 {%- endif %} | |
65 <help><![CDATA[ | |
66 {%- if help %} | |
67 {{ help }} | |
68 {%- else %} | |
69 TODO: Fill in help. | |
70 {%- endif %} | |
71 ]]></help> | |
72 {%- if macros %} | |
73 <expand macro="citations" /> | |
74 {%- else %} | |
75 {%- if doi %} | |
76 <citations> | |
77 {%- for single_doi in doi %} | |
78 <citation type="doi">{{ single_doi }}</citation> | |
79 {%- endfor %} | |
80 </citations> | |
81 {%- endif %} | |
82 {%- endif %} | |
83 </tool> | |
84 """ | |
85 | |
86 MACROS_TEMPLATE = """<macros> | |
87 <xml name="requirements"> | |
88 <requirements> | |
89 {%- for requirement in requirements %} | |
90 {{ requirement }} | |
91 {%- endfor %} | |
92 <yield/> | |
93 {%- for container in containers %} | |
94 {{ container }} | |
95 {%- endfor %} | |
96 </requirements> | |
97 </xml> | |
98 <xml name="stdio"> | |
99 <stdio> | |
100 <exit_code range="1:" /> | |
101 </stdio> | |
102 </xml> | |
103 <xml name="citations"> | |
104 <citations> | |
105 {%- for single_doi in doi %} | |
106 <citation type="doi">{{ single_doi }}</citation> | |
107 {%- endfor %} | |
108 <yield /> | |
109 </citations> | |
110 </xml> | |
111 {%- if version_command %} | |
112 <xml name="version_command"> | |
113 <version_command>{{ version_command }}</version_command> | |
114 </xml> | |
115 {%- endif %} | |
116 </macros> | |
117 """ | |
118 | |
119 | |
120 def build(**kwds): | |
121 # Test case to build up from supplied inputs and outputs, ultimately | |
122 # ignored unless kwds["test_case"] is truthy. | |
123 test_case = TestCase() | |
124 | |
125 command = _find_command(kwds) | |
126 | |
127 _handle_help(kwds) | |
128 | |
129 # process raw inputs | |
130 inputs = kwds.get("input", []) | |
131 del kwds["input"] | |
132 inputs = list(map(Input, inputs or [])) | |
133 | |
134 # alternatively process example inputs | |
135 example_inputs = kwds["example_input"] | |
136 del kwds["example_input"] | |
137 for i, input_file in enumerate(example_inputs or []): | |
138 name = "input%d" % (i + 1) | |
139 inputs.append(Input(input_file, name=name)) | |
140 test_case.params.append((name, input_file)) | |
141 command = _replace_file_in_command(command, input_file, name) | |
142 | |
143 # handle raw outputs (from_work_dir ones) as well as named_outputs | |
144 outputs = kwds.get("output", []) | |
145 del kwds["output"] | |
146 outputs = list(map(Output, outputs or [])) | |
147 | |
148 named_outputs = kwds.get("named_output", []) | |
149 del kwds["named_output"] | |
150 for named_output in (named_outputs or []): | |
151 outputs.append(Output(name=named_output)) | |
152 | |
153 # handle example outputs | |
154 example_outputs = kwds["example_output"] | |
155 del kwds["example_output"] | |
156 for i, output_file in enumerate(example_outputs or []): | |
157 name = "output%d" % (i + 1) | |
158 from_path = output_file | |
159 if output_file in command: | |
160 # Actually found the file in the command, assume it can | |
161 # be specified directly and skip from_work_dir. | |
162 from_path = None | |
163 output = Output(name=name, from_path=from_path) | |
164 outputs.append(output) | |
165 test_case.outputs.append((name, output_file)) | |
166 command = _replace_file_in_command(command, output_file, output.name) | |
167 | |
168 kwds["inputs"] = inputs | |
169 kwds["outputs"] = outputs | |
170 | |
171 # handle requirements and containers | |
172 _handle_requirements(kwds) | |
173 | |
174 kwds["command"] = command | |
175 | |
176 # finally wrap up tests | |
177 tests, test_files = _handle_tests(kwds, test_case) | |
178 kwds["tests"] = tests | |
179 | |
180 # Render tool content from template. | |
181 contents = _render(kwds) | |
182 | |
183 macro_contents = None | |
184 if kwds["macros"]: | |
185 macro_contents = _render(kwds, MACROS_TEMPLATE) | |
186 | |
187 return ToolDescription(contents, macro_contents, test_files) | |
188 | |
189 | |
190 def _render(kwds, template_str=TOOL_TEMPLATE): | |
191 """ Apply supplied template variables to TOOL_TEMPLATE to generate | |
192 the final tool. | |
193 """ | |
194 return templates.render(template_str, **kwds) | |
195 | |
196 | |
197 def _replace_file_in_command(command, specified_file, name): | |
198 """ Replace example file with cheetah variable name in supplied command | |
199 or command template. Be sure to quote the name. | |
200 """ | |
201 # TODO: check if the supplied variant was single quoted already. | |
202 if '"%s"' % specified_file in command: | |
203 # Sample command already wrapped filename in double quotes | |
204 command = command.replace(specified_file, '$%s' % name) | |
205 else: | |
206 # In case of spaces, best to wrap filename in double quotes | |
207 command = command.replace(specified_file, '"$%s"' % name) | |
208 return command | |
209 | |
210 | |
211 def _handle_help(kwds): | |
212 """ Convert supplied help parameters into a help variable for template. | |
213 If help_text is supplied, use as is. If help is specified from a command, | |
214 run the command and use that help text. | |
215 """ | |
216 help_text = kwds.get("help_text") | |
217 if not help_text: | |
218 help_from_command = kwds.get("help_from_command") | |
219 if help_from_command: | |
220 p = subprocess.Popen( | |
221 help_from_command, | |
222 shell=True, | |
223 stdout=subprocess.PIPE, | |
224 stderr=subprocess.STDOUT | |
225 ) | |
226 help_text = p.communicate()[0] | |
227 | |
228 del kwds["help_text"] | |
229 del kwds["help_from_command"] | |
230 | |
231 kwds["help"] = help_text | |
232 | |
233 | |
234 def _handle_tests(kwds, test_case): | |
235 """ Given state built up from handling rest of arguments (test_case) and | |
236 supplied kwds - build tests for template and corresponding test files. | |
237 """ | |
238 test_files = [] | |
239 if kwds["test_case"]: | |
240 tests = [test_case] | |
241 test_files.extend(map(lambda x: x[1], test_case.params)) | |
242 test_files.extend(map(lambda x: x[1], test_case.outputs)) | |
243 else: | |
244 tests = [] | |
245 return tests, test_files | |
246 | |
247 | |
248 def _handle_requirements(kwds): | |
249 """ Convert requirements and containers specified from the command-line | |
250 into abstract format for consumption by the template. | |
251 """ | |
252 requirements = kwds["requirement"] | |
253 del kwds["requirement"] | |
254 requirements = map(Requirement, requirements or []) | |
255 | |
256 container = kwds["container"] | |
257 del kwds["container"] | |
258 containers = map(Container, container or []) | |
259 | |
260 kwds["requirements"] = requirements | |
261 kwds["containers"] = containers | |
262 | |
263 | |
264 def _find_command(kwds): | |
265 """ Find base command from supplied arguments or just return None if no | |
266 such command was supplied (template will just replace this with TODO | |
267 item). | |
268 """ | |
269 command = kwds.get("command") | |
270 if not command: | |
271 command = kwds.get("example_command", None) | |
272 if command: | |
273 del kwds["example_command"] | |
274 return command | |
275 | |
276 | |
277 class ToolDescription(object): | |
278 | |
279 def __init__(self, contents, macro_contents, test_files): | |
280 self.contents = contents | |
281 self.macro_contents = macro_contents | |
282 self.test_files = test_files | |
283 | |
284 | |
285 class Input(object): | |
286 | |
287 def __init__(self, input_description, name=None): | |
288 parts = input_description.split(".") | |
289 name = name or parts[0] | |
290 if len(parts) > 0: | |
291 datatype = ".".join(parts[1:]) | |
292 else: | |
293 datatype = "data" | |
294 | |
295 self.name = name | |
296 self.datatype = datatype | |
297 | |
298 def __str__(self): | |
299 template = '<param type="data" name="{0}" format="{1}" />' | |
300 return template.format(self.name, self.datatype) | |
301 | |
302 | |
303 class Output(object): | |
304 | |
305 def __init__(self, from_path=None, name=None): | |
306 if from_path: | |
307 parts = from_path.split(".") | |
308 name = name or parts[0] | |
309 if len(parts) > 1: | |
310 datatype = ".".join(parts[1:]) | |
311 else: | |
312 datatype = "data" | |
313 else: | |
314 name = name | |
315 datatype = "data" | |
316 | |
317 self.name = name | |
318 self.datatype = datatype | |
319 self.from_path = from_path | |
320 | |
321 def __str__(self): | |
322 if self.from_path: | |
323 return self._from_path_str() | |
324 else: | |
325 return self._named_str() | |
326 | |
327 def _from_path_str(self): | |
328 template = '<data name="{0}" format="{1}" from_work_dir="{2}" />' | |
329 return template.format(self.name, self.datatype, self.from_path) | |
330 | |
331 def _named_str(self): | |
332 template = '<data name="{0}" format="{1}" />' | |
333 return template.format(self.name, self.datatype) | |
334 | |
335 | |
336 class Requirement(object): | |
337 | |
338 def __init__(self, requirement): | |
339 parts = requirement.split("@", 1) | |
340 if len(parts) > 1: | |
341 name = parts[0] | |
342 version = "@".join(parts[1:]) | |
343 else: | |
344 name = parts[0] | |
345 version = None | |
346 self.name = name | |
347 self.version = version | |
348 | |
349 def __str__(self): | |
350 base = '<requirement type="package"{0}>{1}</requirement>' | |
351 if self.version is not None: | |
352 attrs = ' version="{0}"'.format(self.version) | |
353 else: | |
354 attrs = '' | |
355 return base.format(attrs, self.name) | |
356 | |
357 | |
358 class Container(object): | |
359 | |
360 def __init__(self, image_id): | |
361 self.type = "docker" | |
362 self.image_id = image_id | |
363 | |
364 def __str__(self): | |
365 template = '<container type="{0}">{1}</container>' | |
366 return template.format(self.type, self.image_id) | |
367 | |
368 | |
369 class TestCase(object): | |
370 | |
371 def __init__(self): | |
372 self.params = [] | |
373 self.outputs = [] |