Parse the DIAMOND output and yield records. This will be used to read
        original DIAMOND output (either from stdin or from a file) to turn the
        DIAMOND results into Python dictionaries that will then be stored in
        our JSON format.

        @return: A generator that produces C{dict}s containing 'alignments' and
            'query' C{str} keys.

def records(self):
        """
        Parse the DIAMOND output and yield records. This will be used to read
        original DIAMOND output (either from stdin or from a file) to turn the
        DIAMOND results into Python dictionaries that will then be stored in
        our JSON format.

        @return: A generator that produces C{dict}s containing 'alignments' and
            'query' C{str} keys.
        """
        with as_handle(self._filename) as fp:
            previousQtitle = None
            subjectsSeen = {}
            record = {}
            for line in fp:
                line = line[:-1]
                try:
                    (qtitle, stitle, bitscore, evalue, qframe, qseq,
                     qstart, qend, sseq, sstart, send, slen, btop, nident,
                     positive) = line.split('\t')
                except ValueError as e:
                    # We may not be able to find 'nident' and 'positives'
                    # because they were added in version 2.0.3 and will not
                    # be present in any of our JSON output generated before
                    # that. So those values will be None when reading
                    # DIAMOND output without those fields, but that's much
                    # better than no longer being able to read that data.
                    if six.PY2:
                        error = 'need more than 13 values to unpack'
                    else:
                        error = (
                            'not enough values to unpack (expected 15, '
                            'got 13)')
                    if str(e) == error:
                        (qtitle, stitle, bitscore, evalue, qframe,
                         qseq, qstart, qend, sseq, sstart, send, slen,
                         btop) = line.split('\t')
                        nident = positive = None
                    else:
                        raise
                hsp = {
                    'bits': float(bitscore),
                    'btop': btop,
                    'expect': float(evalue),
                    'frame': int(qframe),
                    'identicalCount': None if nident is None else int(nident),
                    'positiveCount': (
                        None if positive is None else int(positive)),
                    'query': qseq,
                    'query_start': int(qstart),
                    'query_end': int(qend),
                    'sbjct': sseq,
                    'sbjct_start': int(sstart),
                    'sbjct_end': int(send),
                }
                if previousQtitle == qtitle:
                    # We have already started accumulating alignments for this
                    # query.
                    if stitle not in subjectsSeen:
                        # We have not seen this subject before, so this is a
                        # new alignment.
                        subjectsSeen.add(stitle)
                        alignment = {
                            'hsps': [hsp],
                            'length': int(slen),
                            'title': stitle,
                        }
                        record['alignments'].append(alignment)
                    else:
                        # We have already seen this subject, so this is another
                        # HSP in an already existing alignment.
                        for alignment in record['alignments']:
                            if alignment['title'] == stitle:
                                alignment['hsps'].append(hsp)
                                break
                else:
                    # All alignments for the previous query id (if any)
                    # have been seen.
                    if previousQtitle is not None:
                        yield record

                    # Start building up the new record.
                    record = {}
                    subjectsSeen = {stitle}
                    alignment = {
                        'hsps': [hsp],
                        'length': int(slen),
                        'title': stitle,
                    }
                    record['alignments'] = [alignment]
                    record['query'] = qtitle

                    previousQtitle = qtitle

            # Yield the last record, if any.
            if record:
                yield record