2020import sys
2121import tempfile
2222import simplejson as json
23+ import copy
24+ import six
2325
2426from ..scripts .instance import import_module
2527
@@ -83,9 +85,9 @@ def generate_boutiques_descriptor(
8385 # Handle compound inputs (inputs that can be of multiple types and are mutually exclusive)
8486 if isinstance (input , list ):
8587 mutex_group_members = []
88+ tool_desc ['command-line' ] += input [0 ]['value-key' ] + " "
8689 for i in input :
8790 tool_desc ['inputs' ].append (i )
88- tool_desc ['command-line' ] += i ['value-key' ] + " "
8991 mutex_group_members .append (i ['id' ])
9092 if verbose :
9193 print ("-> Adding input " + i ['name' ])
@@ -100,6 +102,9 @@ def generate_boutiques_descriptor(
100102 if verbose :
101103 print ("-> Adding input " + input ['name' ])
102104
105+ # Remove the extra space at the end of the command line
106+ tool_desc ['command-line' ] = tool_desc ['command-line' ].strip ()
107+
103108 # Generates input groups
104109 tool_desc ['groups' ] += get_boutiques_groups (interface .inputs .traits (transient = None ).items ())
105110 if len (tool_desc ['groups' ]) == 0 :
@@ -141,14 +146,20 @@ def generate_tool_outputs(outputs, interface, tool_desc, verbose, first_run):
141146 # If this is the first time we are generating outputs, add the full output to the descriptor.
142147 # Otherwise, find the existing output and update its path template if it's still undefined.
143148 if first_run :
144- tool_desc ['output-files' ].append (output )
149+ if isinstance (output , list ):
150+ tool_desc ['output-files' ].extend (output )
151+ if verbose :
152+ print ("-> Adding output " + output [0 ]['name' ])
153+ else :
154+ tool_desc ['output-files' ].append (output )
155+ if verbose :
156+ print ("-> Adding output " + output ['name' ])
145157 else :
146158 for existing_output in tool_desc ['output-files' ]:
147- if output ['id' ] == existing_output ['id' ] and existing_output ['path-template' ] == "" :
159+ if not isinstance (output , list ) and output ['id' ] == existing_output ['id' ] \
160+ and existing_output ['path-template' ] == "" :
148161 existing_output ['path-template' ] = output ['path-template' ]
149162 break
150- if verbose :
151- print ("-> Adding output " + output ['name' ])
152163
153164 if len (tool_desc ['output-files' ]) == 0 :
154165 raise Exception ("Tool has no output." )
@@ -197,9 +208,11 @@ def get_boutiques_input(inputs, interface, input_name, spec,
197208 input_list = []
198209 # Recursively create an input for each trait
199210 for i in range (0 , len (trait_handler .handlers )):
200- input_list .append (get_boutiques_input (inputs , interface , input_name , spec ,
201- ignored_template_inputs , verbose ,
202- ignore_template_numbers , trait_handler .handlers [i ], i ))
211+ inp = get_boutiques_input (inputs , interface , input_name , spec ,
212+ ignored_template_inputs , verbose ,
213+ ignore_template_numbers , trait_handler .handlers [i ], i )
214+ inp ['optional' ] = True
215+ input_list .append (inp )
203216 return input_list
204217
205218 if handler_type == "File" or handler_type == "Directory" :
@@ -227,6 +240,7 @@ def get_boutiques_input(inputs, interface, input_name, spec,
227240 input ['exclusive-maximum' ] = trait_handler .exclude_high
228241
229242 # Deal with list inputs
243+ # TODO handle lists of lists (e.g. FSL ProbTrackX seed input)
230244 if handler_type == "List" :
231245 input ['list' ] = True
232246 trait_type = type (trait_handler .item_trait .trait_type ).__name__
@@ -235,11 +249,13 @@ def get_boutiques_input(inputs, interface, input_name, spec,
235249 input ['type' ] = "Number"
236250 elif trait_type == "Float" :
237251 input ['type' ] = "Number"
252+ elif trait_type == "File" :
253+ input ['type' ] = "File"
238254 else :
239255 input ['type' ] = "String"
240- if trait_handler .minlen is not None :
256+ if trait_handler .minlen != 0 :
241257 input ['min-list-entries' ] = trait_handler .minlen
242- if trait_handler .maxlen is not None :
258+ if trait_handler .maxlen != six . MAXSIZE :
243259 input ['max-list-entries' ] = trait_handler .maxlen
244260
245261 input ['value-key' ] = "[" + input_name .upper (
@@ -266,6 +282,11 @@ def get_boutiques_input(inputs, interface, input_name, spec,
266282 pass
267283 else :
268284 if value_choices is not None :
285+ if all (isinstance (n , int ) for n in value_choices ):
286+ input ['type' ] = "Number"
287+ input ['integer' ] = True
288+ elif all (isinstance (n , float ) for n in value_choices ):
289+ input ['type' ] = "Number"
269290 input ['value-choices' ] = value_choices
270291
271292 # Set Boolean types to Flag (there is no Boolean type in Boutiques)
@@ -316,6 +337,18 @@ def get_boutiques_output(outputs, name, spec, interface, tool_inputs, verbose=Fa
316337 output_value = interface ._list_outputs ()[name ]
317338 except TypeError :
318339 output_value = None
340+ except AttributeError :
341+ output_value = None
342+
343+ # Handle multi-outputs
344+ if isinstance (output_value , list ):
345+ output_list = []
346+ for i in range (0 , len (output_value )):
347+ output_copy = copy .deepcopy (output )
348+ output_copy ['path-template' ] = os .path .relpath (output_value [i ])
349+ output_copy ['id' ] += ("_" + str (i + 1 )) if i > 0 else ""
350+ output_list .append (output_copy )
351+ return output_list
319352
320353 # If an output value is defined, use its relative path
321354 # Otherwise, put blank string and try to fill it on another iteration
0 commit comments