|
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