|
9 | 9 | # Limitations: |
10 | 10 | # * Optional outputs, i.e. outputs that not always produced, may not be detected. They will, however, still be listed |
11 | 11 | # with a placeholder for the path template (either a value key or the output ID) that should be verified and corrected. |
| 12 | +# * Still need to add some fields to the descriptor manually, e.g. url, descriptor-url, path-template-stripped-extensions, etc. |
12 | 13 |
|
13 | 14 | import os |
14 | 15 | import sys |
@@ -77,28 +78,34 @@ def generate_boutiques_descriptor( |
77 | 78 |
|
78 | 79 | # Generates tool inputs |
79 | 80 | for name, spec in sorted(interface.inputs.traits(transient=None).items()): |
80 | | - inp = get_boutiques_input(inputs, interface, name, spec, verbose, ignore_inputs=ignore_inputs) |
81 | | - # Handle compound inputs (inputs that can be of multiple types and are mutually exclusive) |
82 | | - if inp is None: |
| 81 | + # Skip ignored inputs |
| 82 | + if ignore_inputs is not None and name in ignore_inputs: |
83 | 83 | continue |
84 | | - if isinstance(inp, list): |
85 | | - mutex_group_members = [] |
86 | | - tool_desc['command-line'] += inp[0]['value-key'] + " " |
87 | | - for i in inp: |
88 | | - tool_desc['inputs'].append(i) |
89 | | - mutex_group_members.append(i['id']) |
90 | | - if verbose: |
91 | | - print("-> Adding input " + i['name']) |
92 | | - # Put inputs into a mutually exclusive group |
93 | | - tool_desc['groups'].append({'id': inp[0]['id'] + "_group", |
94 | | - 'name': inp[0]['name'] + " group", |
95 | | - 'members': mutex_group_members, |
96 | | - 'mutually-exclusive': True}) |
| 84 | + # If spec has a name source, this means it actually represents an output, so create a |
| 85 | + # Boutiques output from it |
| 86 | + elif spec.name_source and spec.name_template: |
| 87 | + tool_desc['output-files'].append(get_boutiques_output_from_inp(inputs, spec, name)) |
97 | 88 | else: |
98 | | - tool_desc['inputs'].append(inp) |
99 | | - tool_desc['command-line'] += inp['value-key'] + " " |
100 | | - if verbose: |
101 | | - print("-> Adding input " + inp['name']) |
| 89 | + inp = get_boutiques_input(inputs, interface, name, spec, verbose, ignore_inputs=ignore_inputs) |
| 90 | + # Handle compound inputs (inputs that can be of multiple types and are mutually exclusive) |
| 91 | + if isinstance(inp, list): |
| 92 | + mutex_group_members = [] |
| 93 | + tool_desc['command-line'] += inp[0]['value-key'] + " " |
| 94 | + for i in inp: |
| 95 | + tool_desc['inputs'].append(i) |
| 96 | + mutex_group_members.append(i['id']) |
| 97 | + if verbose: |
| 98 | + print("-> Adding input " + i['name']) |
| 99 | + # Put inputs into a mutually exclusive group |
| 100 | + tool_desc['groups'].append({'id': inp[0]['id'] + "_group", |
| 101 | + 'name': inp[0]['name'] + " group", |
| 102 | + 'members': mutex_group_members, |
| 103 | + 'mutually-exclusive': True}) |
| 104 | + else: |
| 105 | + tool_desc['inputs'].append(inp) |
| 106 | + tool_desc['command-line'] += inp['value-key'] + " " |
| 107 | + if verbose: |
| 108 | + print("-> Adding input " + inp['name']) |
102 | 109 |
|
103 | 110 | # Generates input groups |
104 | 111 | tool_desc['groups'] += get_boutiques_groups(interface.inputs.traits(transient=None).items()) |
@@ -148,7 +155,7 @@ def generate_boutiques_descriptor( |
148 | 155 |
|
149 | 156 | # Save descriptor to a file |
150 | 157 | if save: |
151 | | - path = save_path if save_path is not None else os.path.join(os.getcwd(), interface_name + '.json') |
| 158 | + path = save_path or os.path.join(os.getcwd(), interface_name + '.json') |
152 | 159 | with open(path, 'w') as outfile: |
153 | 160 | json.dump(tool_desc, outfile, indent=4, separators=(',', ': ')) |
154 | 161 | if verbose: |
@@ -200,15 +207,9 @@ def get_boutiques_input(inputs, interface, input_name, spec, verbose, handler=No |
200 | 207 | Assumes that: |
201 | 208 | * Input names are unique. |
202 | 209 | """ |
203 | | - |
204 | | - # If spec has a name source, means it's an output, so skip it here. |
205 | | - # Also skip any ignored inputs |
206 | | - if spec.name_source or ignore_inputs is not None and input_name in ignore_inputs: |
207 | | - return None |
208 | | - |
209 | 210 | inp = {} |
210 | 211 |
|
211 | | - if input_number is not None and input_number != 0: # No need to append a number to the first of a list of compound inputs |
| 212 | + if input_number: # No need to append a number to the first of a list of compound inputs |
212 | 213 | inp['id'] = input_name + "_" + str(input_number + 1) |
213 | 214 | else: |
214 | 215 | inp['id'] = input_name |
@@ -302,20 +303,18 @@ def get_boutiques_input(inputs, interface, input_name, spec, verbose, handler=No |
302 | 303 | if handler_type == "InputMultiObject": |
303 | 304 | inp['type'] = "File" |
304 | 305 | inp['list'] = True |
| 306 | + if spec.sep: |
| 307 | + inp['list-separator'] = spec.sep |
305 | 308 |
|
306 | 309 | inp['value-key'] = "[" + input_name.upper( |
307 | 310 | ) + "]" # assumes that input names are unique |
308 | 311 |
|
309 | | - # Add the command line flag specified by argstr |
310 | | - # If no argstr is provided and input type is Flag, create a flag from the name |
311 | | - if spec.argstr: |
312 | | - if "=" in spec.argstr: |
313 | | - inp['command-line-flag'] = spec.argstr.split("=")[0].strip() |
314 | | - inp['command-line-flag-separator'] = "=" |
315 | | - elif spec.argstr.split("%")[0]: |
316 | | - inp['command-line-flag'] = spec.argstr.split("%")[0].strip() |
317 | | - elif inp['type'] == "Flag": |
318 | | - inp['command-line-flag'] = ("--%s" % input_name + " ").strip() |
| 312 | + flag, flag_sep = get_command_line_flag(spec, inp['type'] == "Flag", input_name) |
| 313 | + |
| 314 | + if flag is not None: |
| 315 | + inp['command-line-flag'] = flag |
| 316 | + if flag_sep is not None: |
| 317 | + inp['command-line-flag-separator'] = flag_sep |
319 | 318 |
|
320 | 319 | inp['description'] = get_description_from_spec(inputs, input_name, spec) |
321 | 320 | if not (hasattr(spec, "mandatory") and spec.mandatory): |
@@ -384,42 +383,32 @@ def get_boutiques_output(outputs, name, spec, interface, tool_inputs): |
384 | 383 | output_value = None |
385 | 384 | except AttributeError: |
386 | 385 | output_value = None |
| 386 | + except KeyError: |
| 387 | + output_value = None |
387 | 388 |
|
388 | 389 | # Handle multi-outputs |
389 | | - if isinstance(output_value, list): |
| 390 | + if isinstance(output_value, list) or type(spec.handler).__name__ == "OutputMultiObject": |
390 | 391 | output['list'] = True |
391 | | - # Check if all extensions are the same |
392 | | - extensions = [] |
393 | | - for val in output_value: |
394 | | - extensions.append(os.path.splitext(val)[1]) |
395 | | - # If extensions all the same, set path template as wildcard + extension |
396 | | - # Otherwise just use a wildcard |
397 | | - if len(set(extensions)) == 1: |
398 | | - output['path-template'] = "*" + extensions[0] |
399 | | - else: |
400 | | - output['path-template'] = "*" |
401 | | - return output |
| 392 | + if output_value: |
| 393 | + # Check if all extensions are the same |
| 394 | + extensions = [] |
| 395 | + for val in output_value: |
| 396 | + extensions.append(os.path.splitext(val)[1]) |
| 397 | + # If extensions all the same, set path template as wildcard + extension |
| 398 | + # Otherwise just use a wildcard |
| 399 | + if len(set(extensions)) == 1: |
| 400 | + output['path-template'] = "*" + extensions[0] |
| 401 | + else: |
| 402 | + output['path-template'] = "*" |
| 403 | + return output |
402 | 404 |
|
403 | 405 | # If an output value is defined, use its relative path, if one exists. |
404 | | - # If no relative path, look for an input with the same name containing a name source |
405 | | - # and name template. Otherwise, put blank string as placeholder and try to fill it on |
| 406 | + # Otherwise, put blank string as placeholder and try to fill it on |
406 | 407 | # another iteration. |
407 | | - output['path-template'] = "" |
408 | | - |
409 | 408 | if output_value: |
410 | 409 | output['path-template'] = os.path.relpath(output_value) |
411 | 410 | else: |
412 | | - for inp_name, inp_spec in sorted(interface.inputs.traits(transient=None).items()): |
413 | | - if inp_name == name and inp_spec.name_source and inp_spec.name_template: |
414 | | - if isinstance(inp_spec.name_source, list): |
415 | | - source = inp_spec.name_source[0] |
416 | | - else: |
417 | | - source = inp_spec.name_source |
418 | | - output['path-template'] = inp_spec.name_template.replace("%s", "[" + source.upper() + "]") |
419 | | - output['value-key'] = "[" + name.upper() + "]" |
420 | | - if inp_spec.argstr and inp_spec.argstr.split("%")[0]: |
421 | | - output['command-line-flag'] = inp_spec.argstr.split("%")[0].strip() |
422 | | - break |
| 411 | + output['path-template'] = "" |
423 | 412 |
|
424 | 413 | return output |
425 | 414 |
|
@@ -535,4 +524,52 @@ def reorder_cmd_line_args(cmd_line, interface, ignore_inputs=None): |
535 | 524 | continue |
536 | 525 | positional_args.append(item[1]) |
537 | 526 |
|
538 | | - return interface_name + " " + " ".join(positional_args) + " " + ((last_arg + " ") if last_arg else "") + " ".join(non_positional_args) |
| 527 | + return interface_name + " " +\ |
| 528 | + ((" ".join(positional_args) + " ") if len(positional_args) > 0 else "") +\ |
| 529 | + ((last_arg + " ") if last_arg else "") +\ |
| 530 | + " ".join(non_positional_args) |
| 531 | + |
| 532 | + |
| 533 | +def get_command_line_flag(input_spec, is_flag_type=False, input_name=None): |
| 534 | + ''' |
| 535 | + Generates the command line flag for a given input |
| 536 | + ''' |
| 537 | + flag, flag_sep = None, None |
| 538 | + if input_spec.argstr: |
| 539 | + if "=" in input_spec.argstr: |
| 540 | + flag = input_spec.argstr.split("=")[0].strip() |
| 541 | + flag_sep = "=" |
| 542 | + elif input_spec.argstr.split("%")[0]: |
| 543 | + flag = input_spec.argstr.split("%")[0].strip() |
| 544 | + elif is_flag_type: |
| 545 | + flag = ("--%s" % input_name + " ").strip() |
| 546 | + return flag, flag_sep |
| 547 | + |
| 548 | + |
| 549 | +def get_boutiques_output_from_inp(inputs, inp_spec, inp_name): |
| 550 | + ''' |
| 551 | + Takes a Nipype input representing an output file and generates a Boutiques output for it |
| 552 | + ''' |
| 553 | + output = {} |
| 554 | + output['name'] = inp_name.replace('_', ' ').capitalize() |
| 555 | + output['id'] = inp_name |
| 556 | + output['optional'] = True |
| 557 | + output['description'] = get_description_from_spec(inputs, inp_name, inp_spec) |
| 558 | + if not (hasattr(inp_spec, "mandatory") and inp_spec.mandatory): |
| 559 | + output['optional'] = True |
| 560 | + else: |
| 561 | + output['optional'] = False |
| 562 | + if inp_spec.usedefault: |
| 563 | + output['default-value'] = inp_spec.default_value()[1] |
| 564 | + if isinstance(inp_spec.name_source, list): |
| 565 | + source = inp_spec.name_source[0] |
| 566 | + else: |
| 567 | + source = inp_spec.name_source |
| 568 | + output['path-template'] = inp_spec.name_template.replace("%s", "[" + source.upper() + "]") |
| 569 | + output['value-key'] = "[" + inp_name.upper() + "]" |
| 570 | + flag, flag_sep = get_command_line_flag(inp_spec) |
| 571 | + if flag is not None: |
| 572 | + output['command-line-flag'] = flag |
| 573 | + if flag_sep is not None: |
| 574 | + output['command-line-flag-separator'] = flag_sep |
| 575 | + return output |
0 commit comments