@@ -521,43 +521,93 @@ def template_update(inputs, map_copyfiles=None):
521521 f"fields with output_file_template"
522522 "has to be a string or Union[str, bool]"
523523 )
524- inp_val_set = getattr (inputs , fld .name )
524+ dict_ [fld .name ] = template_update_single (field = fld , inputs_dict = dict_ )
525+ # using is and == so it covers list and numpy arrays
526+ updated_templ_dict = {
527+ k : v
528+ for k , v in dict_ .items ()
529+ if not (getattr (inputs , k ) is v or getattr (inputs , k ) == v )
530+ }
531+ return updated_templ_dict
532+
533+
534+ def template_update_single (field , inputs_dict , spec_type = "input" ):
535+ """Update a single template from the input_spec or output_spec
536+ based on the value from inputs_dict
537+ (checking the types of the fields, that have "output_file_template)"
538+ """
539+ from .specs import File
540+
541+ if spec_type == "input" :
542+ if field .type not in [str , ty .Union [str , bool ]]:
543+ raise Exception (
544+ f"fields with output_file_template"
545+ "has to be a string or Union[str, bool]"
546+ )
547+ inp_val_set = inputs_dict [field .name ]
525548 if inp_val_set is not attr .NOTHING and not isinstance (inp_val_set , (str , bool )):
526- raise Exception (f"{ fld .name } has to be str or bool, but { inp_val_set } set" )
527- if isinstance (inp_val_set , bool ) and fld .type is str :
528549 raise Exception (
529- f"type of { fld .name } is str, consider using Union[ str, bool] "
550+ f"{ field .name } has to be str or bool, but { inp_val_set } set "
530551 )
552+ if isinstance (inp_val_set , bool ) and field .type is str :
553+ raise Exception (
554+ f"type of { field .name } is str, consider using Union[str, bool]"
555+ )
556+ elif spec_type == "output" :
557+ if field .type is not File :
558+ raise Exception (
559+ f"output { field .name } should be a File, but { field .type } set as the type"
560+ )
561+ else :
562+ raise Exception (f"spec_type can be input or output, but { spec_type } provided" )
563+ if spec_type == "input" and isinstance (inputs_dict [field .name ], str ):
564+ return inputs_dict [field .name ]
565+ elif spec_type == "input" and inputs_dict [field .name ] is False :
566+ # if input fld is set to False, the fld shouldn't be used (setting NOTHING)
567+ return attr .NOTHING
568+ else : # inputs_dict[field.name] is True or spec_type is output
569+ template = field .metadata ["output_file_template" ]
570+ # as default, we assume that keep_extension is True
571+ keep_extension = field .metadata .get ("keep_extension" , True )
572+ value = _template_formatting (
573+ template , inputs_dict , keep_extension = keep_extension
574+ )
575+ return value
531576
532- if isinstance (inp_val_set , str ):
533- dict_ [fld .name ] = inp_val_set
534- elif inp_val_set is False :
535- # if False, the field should not be used, so setting attr.NOTHING
536- dict_ [fld .name ] = attr .NOTHING
537- else : # True or attr.NOTHING
538- template = fld .metadata ["output_file_template" ]
539- value = template .format (** dict_ )
540- value = removing_nothing (value )
541- dict_ [fld .name ] = value
542- return {k : v for k , v in dict_ .items () if getattr (inputs , k ) is not v }
543-
544-
545- def removing_nothing (template_str ):
546- """ removing all fields that had NOTHING"""
547- if "NOTHING" not in template_str :
548- return template_str
549- regex = re .compile (r"[^a-zA-Z_\-]" )
550- fields_str = regex .sub (" " , template_str )
551- for fld in fields_str .split ():
552- if "NOTHING" in fld :
553- template_str = template_str .replace (fld , "" )
554- return (
555- template_str .replace ("[ " , "[" )
556- .replace (" ]" , "]" )
557- .replace (",]" , "]" )
558- .replace ("[," , "[" )
559- .strip ()
560- )
577+
578+ def _template_formatting (template , inputs_dict , keep_extension = True ):
579+ """Formatting a single template based on values from inputs_dict.
580+ Taking into account that field values and template could have file extensions
581+ (assuming that if template has extension, the field value extension is removed,
582+ if field has extension, and no template extension, than it is moved to the end),
583+ """
584+ inp_fields = re .findall ("{\w+}" , template )
585+ if len (inp_fields ) == 0 :
586+ return template
587+ elif len (inp_fields ) == 1 :
588+ fld_name = inp_fields [0 ][1 :- 1 ]
589+ fld_value = inputs_dict [fld_name ]
590+ if fld_value is attr .NOTHING :
591+ return attr .NOTHING
592+ fld_value = str (fld_value ) # in case it's a path
593+ filename , * ext = fld_value .split ("." , maxsplit = 1 )
594+ # if keep_extension is False, the extensions are removed
595+ if keep_extension is False :
596+ ext = []
597+ if template .endswith (inp_fields [0 ]):
598+ # if no suffix added in template, the simplest formatting should work
599+ # recreating fld_value with the updated extension
600+ fld_value_upd = "." .join ([filename ] + ext )
601+ formatted_value = template .format (** {fld_name : fld_value_upd })
602+ elif "." not in template : # the template doesn't have its own extension
603+ # if the fld_value has extension, it will be moved to the end
604+ formatted_value = "." .join ([template .format (** {fld_name : filename })] + ext )
605+ else : # template has its own extension
606+ # removing fld_value extension if any
607+ formatted_value = template .format (** {fld_name : filename })
608+ return formatted_value
609+ else :
610+ raise NotImplementedError ("should we allow for more args in the template?" )
561611
562612
563613def is_local_file (f ):
0 commit comments