docs: def read_and_save_data(info_df, raw_dir, sep=";", force_raw=False,
                         force_cellpy=False,
                         export_cycles=False, shifted_cycles=False,
                         export_raw=True,
                         export_ica=False, save=True, use_cellpy_stat_file=False,
                         parent_level="CellpyData",
                         last_cycle=None,
                         ):
      """Reads and saves cell data defined by the info-DataFrame.
  
      The function iterates through the ``info_df`` and loads data from the runs.
      It saves individual data for each run (if selected), as well as returns a
      list of ``cellpy`` summary DataFrames, a list of the indexes (one for each
      run; same as used as index in the ``info_df``), as well as a list with
      indexes of runs (cells) where an error was encountered during loading.
  
      Args:
          use_cellpy_stat_file: use the stat file to perform the calculations.
          info_df: pandas.DataFrame with information about the runs.
          raw_dir: path to location where you want to save raw data.
          sep: delimiter to use when exporting to csv.
          force_raw: load raw data even-though cellpy-file is up-to-date.
          force_cellpy: load cellpy files even-though cellpy-file is not
              up-to-date.
          export_cycles: set to True for exporting cycles to csv.
          shifted_cycles: set to True for exporting the cycles with a cumulated
              shift.
          export_raw: set to True for exporting raw data to csv.
          export_ica: set to True for calculating and exporting dQ/dV to csv.
          save: set to False to prevent saving a cellpy-file.
          parent_level: optional, should use "cellpydata" for older hdf5-files and
              default for newer ones.
  
      Returns: frames (list of cellpy summary DataFrames), keys (list of indexes),
          errors (list of indexes that encountered errors).
      """
      no_export = False
      do_export_dqdv = export_ica
      keys = []
      frames = []
      number_of_runs = len(info_df)
      counter = 0
      errors = []
  
      for indx, row in info_df.iterrows():
          counter += 1
          h_txt = "[" + counter * "|" + (number_of_runs - counter) * "." + "]"
          l_txt = "starting to process file # %i (index=%s)" % (counter, indx)
          logger.debug(l_txt)
          print(h_txt)
          if not row.raw_file_names and not force_cellpy:
              logger.info("File(s) not found!")
              logger.info(indx)
              logger.debug("File(s) not found for index=%s" % indx)
              errors.append(indx)
              continue
          else:
              logger.info(f"Processing {indx}")
          cell_data = cellreader.CellpyData()
          if not force_cellpy:
              logger.info("setting cycle mode (%s)..." % row.cell_type)
              cell_data.set_cycle_mode(row.cell_type)
  
          logger.info("loading cell")
          if not force_cellpy:
              logger.info("not forcing")
              try:
                  cell_data.loadcell(raw_files=row.raw_file_names,
                                     cellpy_file=row.cellpy_file_names,
                                     mass=row.masses, summary_on_raw=True,
                                     force_raw=force_raw,
                                     use_cellpy_stat_file=use_cellpy_stat_file)
              except Exception as e:
                  logger.debug('Failed to load: ' + str(e))
                  errors.append("loadcell:" + str(indx))
                  continue
          else:
              logger.info("forcing")
              try:
                  cell_data.load(row.cellpy_file_names, parent_level=parent_level)
              except Exception as e:
                  logger.info(f"Critical exception encountered {type(e)} "
                              "- skipping this file")
                  logger.debug('Failed to load. Error-message: ' + str(e))
                  errors.append("load:" + str(indx))
                  continue
  
          if not cell_data.check():
              logger.info("...not loaded...")
              logger.debug("Did not pass check(). Could not load cell!")
              errors.append("check:" + str(indx))
              continue
  
          logger.info("...loaded successfully...")
          keys.append(indx)
  
          summary_tmp = cell_data.dataset.dfsummary
          logger.info("Trying to get summary_data")
          if summary_tmp is None:
              logger.info("No existing summary made - running make_summary")
              cell_data.make_summary(find_end_voltage=True, find_ir=True)
  
          if summary_tmp.index.name == b"Cycle_Index":
              logger.debug("Strange: 'Cycle_Index' is a byte-string")
              summary_tmp.index.name = 'Cycle_Index'
  
          if not summary_tmp.index.name == "Cycle_Index":
              logger.debug("Setting index to Cycle_Index")
              # check if it is a byte-string
              if b"Cycle_Index" in summary_tmp.columns:
                  logger.debug("Seems to be a byte-string in the column-headers")
                  summary_tmp.rename(columns={b"Cycle_Index": 'Cycle_Index'},
                                     inplace=True)
              summary_tmp.set_index("Cycle_Index", inplace=True)
  
          frames.append(summary_tmp)
  
          if save:
              if not row.fixed:
                  logger.info("saving cell to %s" % row.cellpy_file_names)
                  cell_data.ensure_step_table = True
                  cell_data.save(row.cellpy_file_names)
              else:
                  logger.debug("saving cell skipped (set to 'fixed' in info_df)")
  
          if no_export:
              continue
  
          if export_raw:
              logger.info("exporting csv")
              cell_data.to_csv(raw_dir, sep=sep, cycles=export_cycles,
                               shifted=shifted_cycles, raw=export_raw,
                               last_cycle=last_cycle)
  
          if do_export_dqdv:
              logger.info("exporting dqdv")
              try:
                  export_dqdv(cell_data, savedir=raw_dir, sep=sep,
                              last_cycle=last_cycle)
              except Exception as e:
                  logging.error("Could not make/export dq/dv data")
                  logger.debug("Failed to make/export "
                               "dq/dv data (%s): %s" % (indx, str(e)))
                  errors.append("ica:" + str(indx))
  
      if len(errors) > 0:
          logger.error("Finished with errors!")
          logger.debug(errors)
      else:
          logger.info("Finished")
  
      return frames, keys, errors