Populate this index from a file. Input format is just a tab-separated file,
    one record per line. The last column is the file location for the record
    and all columns before that are collectively considered to be the hash key
    for that record (which is probably only 1 column, but this allows us to
    permit tabs in hash keys). Lines consisting only of whitespace are skipped.

    :param fh:            filename or stream-like object to read from.
    :param indexed_fh:    either the filename of the indexed file or handle to
                          it.
    :param rec_iterator:  a function that will return an interator for the
                          indexed file type (not the iterator for the file
                          itself). This function must take a single argument
                          which is the name the file to iterate over, or a
                          stream like object similar to a filestream.
    :param rec_hash_func: a function that accepts the record type produced by
                          the iterator and produces a unique hash for each
                          record.
    :param parse_hash:    a function to convert the string representation of
                          the hash into whatever type is needed. By default,
                          we just leave these as strings.
    :param flush:         remove everything currently in the index and discard
                          any details about a file that is already
                          fully/partially indexed by this object. This is the
                          default behavior. If False, then data from <fh> is
                          just added to the existing index data (potentially
                          overwriting some of it) and the existing index can
                          continue to be used as before.
    :param no_reindex:    if True, after loading the index, a missing key will
                          cause an exception, rather than trigger re-scanning
                          the indexed file for the associated record. The only
                          reason to set this to False would be if your index
                          was incomplete.
    :param verbose:       output status message to STDERR about progress
                          reading the index (if possible).

    :raise IndexError: on malformed line in input file/stream

def read_index(self, fh, indexed_fh, rec_iterator=None,
                 rec_hash_func=None, parse_hash=str, flush=True,
                 no_reindex=True, verbose=False):
    """
    Populate this index from a file. Input format is just a tab-separated file,
    one record per line. The last column is the file location for the record
    and all columns before that are collectively considered to be the hash key
    for that record (which is probably only 1 column, but this allows us to
    permit tabs in hash keys). Lines consisting only of whitespace are skipped.

    :param fh:            filename or stream-like object to read from.
    :param indexed_fh:    either the filename of the indexed file or handle to
                          it.
    :param rec_iterator:  a function that will return an interator for the
                          indexed file type (not the iterator for the file
                          itself). This function must take a single argument
                          which is the name the file to iterate over, or a
                          stream like object similar to a filestream.
    :param rec_hash_func: a function that accepts the record type produced by
                          the iterator and produces a unique hash for each
                          record.
    :param parse_hash:    a function to convert the string representation of
                          the hash into whatever type is needed. By default,
                          we just leave these as strings.
    :param flush:         remove everything currently in the index and discard
                          any details about a file that is already
                          fully/partially indexed by this object. This is the
                          default behavior. If False, then data from <fh> is
                          just added to the existing index data (potentially
                          overwriting some of it) and the existing index can
                          continue to be used as before.
    :param no_reindex:    if True, after loading the index, a missing key will
                          cause an exception, rather than trigger re-scanning
                          the indexed file for the associated record. The only
                          reason to set this to False would be if your index
                          was incomplete.
    :param verbose:       output status message to STDERR about progress
                          reading the index (if possible).

    :raise IndexError: on malformed line in input file/stream
    """
    # set the record iterator and hash functions, if they were given
    if rec_iterator is not None:
      self.record_iterator = rec_iterator
    if rec_hash_func is not None:
      self.record_hash_function = rec_hash_func

    # disable re-indexing?
    self._no_reindex = no_reindex

    # figure out what kind of index identifier we got: handle or filename?
    handle = fh
    try:
      handle = open(fh)
    except TypeError:
      # okay, not a filename, we'll try treating it as a stream to read from.
      pass

    # clear this index?
    if flush:
      self._index = {}
      self._indexed_file_handle = None
      self._indexed_file_name = None

    # replace the name/handle for the indexed file
    indexed_fn = None
    try:
      # try treating this as a filename
      self.indexed_file = (indexed_fh, None)
      indexed_fn = indexed_fh
    except TypeError:
      try:
        # try treating this as a file handle
        self.indexed_file = (None, indexed_fh)
      except TypeError:
        fn = " from " + str(fh) if indexed_fn is not None else ""
        raise IndexError("failed to read index" + fn + "; "
                         "reason: expected indexed filename or stream-like "
                         "object, got " + str(type(indexed_fh)))

    # try to get an idea of how much data we have...
    if verbose:
      try:
        total = os.path.getsize(handle.name)
        pind = ProgressIndicator(totalToDo=total, messagePrefix="completed",
                                 messageSuffix="of loading " + handle.name)
      except AttributeError as e:
        sys.stderr.write(str(e))
        sys.stderr.write("completed [unknown] of loading index")
        verbose = False

    # read the index file and populate this object
    for line in handle:
      line = line.rstrip()

      if verbose:
        pind.done = handle.tell()
        pind.showProgress()

      if line.isspace():
        continue
      parts = line.split("\t")
      if len(parts) < 2:
        raise IndexError("failed to parse line: '" + line + "'")
      key = parse_hash("\t".join(parts[:-1]))
      value = parts[-1]
      self._index[key] = int(value)