[docs]defgroup_by_suffix(file_list):""" Group files by suffix. Parameters: file_list (list): List of file paths. Returns: dict: Dictionary with suffixes as keys and file lists as values. """suffix_groups={}forfile_pathinfile_list:name,ext=get_full_extension(file_path)result=os.path.basename(name).rsplit('_',1)iflen(result)==2:suffix_groups.setdefault(result[1],[]).append(file_path)else:suffix_groups.setdefault(result[0],[]).append(file_path)returnsuffix_groups
[docs]defparse_bids_filename(file_path):"""Split a filename into BIDS-relevant components. Parameters: file_path (str): Path to be parsed. Returns: dict: Dictionary with keys 'basename', 'suffix', 'prefix', 'ext', 'bad', and 'entities'. Notes: - Splits into BIDS suffix, extension, and a dictionary of entity name-value pairs. """name,ext=get_full_extension(file_path.strip())basename=os.path.basename(name)name_dict={"basename":basename,"suffix":None,"prefix":None,"ext":ext,"bad":[],"entities":{}}ifnotbasename:returnname_dictentity_pieces=basename.rsplit('_',1)# Case: No underscore in filename → could be a single entity (e.g., "task-blech.tsv")iflen(entity_pieces)==1:entity_count=entity_pieces[0].count('-')ifentity_count>1:name_dict["bad"].append(entity_pieces[0])elifentity_count==1:# Looks like an entity-type pairupdate_entity(name_dict,entity_pieces[0])else:name_dict["suffix"]=entity_pieces[0]returnname_dict# Case: Underscore present → split into entities + possible suffixrest,suffix=entity_pieces# If suffix is a valid entity-type pair (e.g., "task-motor"), move it into the entity dictionaryif'-'insuffixandsuffix.count('-')==1:update_entity(name_dict,suffix)else:name_dict["suffix"]=suffix# Look for prefix - first entity piece without a hyphenentity_pieces=rest.split('_')if'-'notinentity_pieces[0]:name_dict["prefix"]=entity_pieces[0]delentity_pieces[0]iflen(entity_pieces)==0:returnname_dict# Process entitiesforentityinentity_pieces:update_entity(name_dict,entity)returnname_dict
[docs]defupdate_entity(name_dict,entity):"""Update the dictionary with a new entity. Parameters: name_dict (dict): Dictionary of entities. entity (str): Entity to be added. """parts=entity.split('-')iflen(parts)==2andall(parts):# Valid entity pairname_dict["entities"][parts[0]]=parts[1]else:name_dict["bad"].append(entity)
[docs]defwalk_back(root_path,file_path):file_path=os.path.abspath(file_path)source_dir=os.path.dirname(file_path)root_path=os.path.abspath(root_path)# Normalize root_path for cross-platform supportwhilesource_dirandsource_dir!=root_path:candidates=get_candidates(source_dir,file_path)iflen(candidates)==1:yieldcandidates[0]eliflen(candidates)>1:raiseException({"code":"MULTIPLE_INHERITABLE_FILES","location":candidates[0],"affects":file_path,"issueMessage":f"Candidate files: {candidates}",})# Stop when we reach the root directory (handling Windows and Unix)new_source_dir=os.path.dirname(source_dir)ifnew_source_dir==source_dirornew_source_dir==root_path:breaksource_dir=new_source_dir