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 = []