Iterator for repeat masker alignment files; yields multiple alignment objects.

  Iterate over a file/stream of full repeat alignments in the repeatmasker
  format. Briefly, this format is as follows: each record (alignment) begins
  with a header line (see _rm_parse_header_line documentation for details of
  header format), followed by the alignment itself (example below) and finally
  a set of key-value meta-data pairs.

  The actual alignment looks like this::

    chr1               11 CCCTGGAGATTCTTATT--AGTGATTTGGGCT 41
                             ii        v   -- v  i i    v
    C MER5B#DNA/hAT    10 CCCCAGAGATTCTGATTTAATTGGTCTGGGGT 42

    chr1               42 GACTG 47
                           v
    C MER5B#DNA/hAT    43 CACTG 48

  The 'C' indicates that its the reverse complement of the consensus. The
  central string gives information about matches; "-" indicates an
  insertion/deletion, "i" a transition (G<->A, C<->T) and "v" a transversion
  (all other substitutions).

  :param fh:             filename or stream-like object to read from.
  :param index_friendly: if True, we will ensure the file/stream
                         position is before the start of the record when we
                         yield it; this requires the ability to seek within
                         the stream though, so if iterating over a
                         stream wtihout that ability, you'll have to set this
                         to false. Further, this will disable buffering for
                         the file, to ensure file.tell() behaves correctly,
                         so a performance hit will be incurred.
  :param verbose:        if true, output progress messages to stderr.

def repeat_masker_alignment_iterator(fn, index_friendly=True, verbose=False):
  """
  Iterator for repeat masker alignment files; yields multiple alignment objects.

  Iterate over a file/stream of full repeat alignments in the repeatmasker
  format. Briefly, this format is as follows: each record (alignment) begins
  with a header line (see _rm_parse_header_line documentation for details of
  header format), followed by the alignment itself (example below) and finally
  a set of key-value meta-data pairs.

  The actual alignment looks like this::

    chr1               11 CCCTGGAGATTCTTATT--AGTGATTTGGGCT 41
                             ii        v   -- v  i i    v
    C MER5B#DNA/hAT    10 CCCCAGAGATTCTGATTTAATTGGTCTGGGGT 42

    chr1               42 GACTG 47
                           v
    C MER5B#DNA/hAT    43 CACTG 48

  The 'C' indicates that its the reverse complement of the consensus. The
  central string gives information about matches; "-" indicates an
  insertion/deletion, "i" a transition (G<->A, C<->T) and "v" a transversion
  (all other substitutions).

  :param fh:             filename or stream-like object to read from.
  :param index_friendly: if True, we will ensure the file/stream
                         position is before the start of the record when we
                         yield it; this requires the ability to seek within
                         the stream though, so if iterating over a
                         stream wtihout that ability, you'll have to set this
                         to false. Further, this will disable buffering for
                         the file, to ensure file.tell() behaves correctly,
                         so a performance hit will be incurred.
  :param verbose:        if true, output progress messages to stderr.
  """
  # step 1 -- build our iterator for the stream..
  try:
    fh = open(fn)
  except (TypeError):
    fh = fn
  iterable = fh
  if index_friendly:
    iterable = iter(fh.readline, '')

  # build progress indicator, if we want one and we're able to
  if verbose:
    try:
      m_fn = ": " + fh.name
    except TypeError:
      m_fn = ""
    try:
      current = fh.tell()
      fh.seek(0, 2)
      total_progress = fh.tell()
      fh.seek(current)
      pind = ProgressIndicator(totalToDo=total_progress,
                               messagePrefix="completed",
                               messageSuffix="of processing repeat-masker "
                                             "alignment file" + m_fn)
    except IOError:
      pind = None

  old_fh_pos = None
  new_fh_pos = fh.tell()

  s1 = None
  s2 = None
  s1_name = None
  s2_name = None
  s1_start = None
  s1_end = None
  s2_start = None
  s2_end = None
  meta_data = None
  alignment_line_counter = 0
  alig_l_space = 0
  prev_seq_len = 0
  rev_comp_match = None
  remaining_repeat = None
  remaining_genomic = None

  for line in iterable:
    if verbose and pind is not None:
      pind.done = fh.tell()
      pind.showProgress()

    if index_friendly:
      old_fh_pos = new_fh_pos
      new_fh_pos = fh.tell()
    line = line.rstrip()
    if line.lstrip() == "" and alignment_line_counter % 3 != 1:
      continue

    s_pres_split = re.split(r'(\s+)', line)
    parts = [x for x in s_pres_split if not (x.isspace() or x == "")]

    n = len(parts)
    for i in REPEATMASKER_FIELDS_TO_TRIM:
      if n >= i + 1:
        parts[i] = parts[i].strip()

    # decide what to do with this line -- is it a header line, part of the
    # alignment or a meta-data key-value line
    if alignment_line_counter % 3 == 1:
      if (REPEATMASKER_VALIDATE_MUTATIONS and
         not _rm_is_valid_annotation_line(line)):
        raise IOError("invalid mutation line: " + line)
      l_space = _rm_compute_leading_space(s_pres_split) - alig_l_space
      pad_right = prev_seq_len - (l_space + len(line.strip()))
      meta_data[ANNOTATION_KEY] += ((' ' * l_space) + line.strip() +
                                    (' ' * pad_right))
      alignment_line_counter += 1
    elif _rm_is_header_line(parts, n):
      if not (s1 is None and s2 is None and meta_data is None):
        if ANNOTATION_KEY in meta_data:
          meta_data[ANNOTATION_KEY] = meta_data[ANNOTATION_KEY].rstrip()
        if index_friendly:
          fh.seek(old_fh_pos)
        ss1 = Sequence(s1_name, s1, s1_start, s1_end, "+", remaining_genomic)
        s2s = "-" if rev_comp_match else "+"
        ss2 = Sequence(s2_name, s2, s2_start, s2_end, s2s, remaining_repeat)
        yield PairwiseAlignment(ss1, ss2, meta_data)
        if index_friendly:
          fh.seek(new_fh_pos)
      meta_data = {}
      s1 = ""
      s2 = ""
      s1_name, s2_name = _rm_get_names_from_header(parts)
      s1_start, s1_end = _rm_get_reference_coords_from_header(parts)
      s2_start, s2_end = _rm_get_repeat_coords_from_header(parts)
      rev_comp_match = _rm_is_reverse_comp_match(parts)
      remaining_repeat = _rm_get_remaining_repeat_from_header(parts)
      remaining_genomic = _rm_get_remaining_genomic_from_header(parts)

      _rm_parse_header_line(parts, meta_data)
      alignment_line_counter = 0
    elif _rm_is_alignment_line(parts, s1_name, s2_name):
      alignment_line_counter += 1
      name, seq = _rm_extract_sequence_and_name(parts, s1_name, s2_name)
      if name == s1_name:
        s1 += seq
      elif name == s2_name:
        s2 += seq
      alig_l_space = _rm_compute_leading_space_alig(s_pres_split, seq)
      prev_seq_len = len(seq)
    else:
      k, v = _rm_parse_meta_line(parts)
      meta_data[k] = v
  if index_friendly:
    fh.seek(old_fh_pos)
  ss1 = Sequence(s1_name, s1, s1_start, s1_end, "+", remaining_genomic)
  s2s = "-" if rev_comp_match else "+"
  ss2 = Sequence(s2_name, s2, s2_start, s2_end, s2s, remaining_repeat)
  yield PairwiseAlignment(ss1, ss2, meta_data)
  if index_friendly:
    fh.seek(new_fh_pos)