docs: def consensus(aln, weights=None, gap_threshold=0.5, simple=False, trim_ends=True):
      """Get the consensus of an alignment, as a string.
  
      Emit gap characters for majority-gap columns; apply various strategies to
      choose the consensus amino acid type for the remaining columns.
  
      Parameters
      ----------
  
      simple : bool
          If True, use simple plurality to determine the consensus amino acid
          type, without weighting sequences for similarity. Otherwise, weight
          sequences for similarity and use relative entropy to choose the
          consensus amino acid type.
      weights : dict or None
          Sequence weights. If given, used to calculate amino acid frequencies;
          otherwise calculated within this function (i.e. this is a way to speed
          up the function if sequence weights have already been calculated).
          Ignored in 'simple' mode.
      trim_ends : bool
          If False, stretch the consensus sequence to include the N- and C-tails
          of the alignment, even if those flanking columns are mostly gap
          characters. This avoids terminal gaps in the consensus (needed for
          MAPGAPS).
      gap_threshold : float
          If the proportion of gap characters in a column is greater than or equal
          to this value (after sequence weighting, if applicable), then the
          consensus character emitted will be a gap instead of an amino acid type.
  
      """
      # Choose your algorithms!
      if simple:
          # Use the simple, unweighted algorithm
          col_consensus = make_simple_col_consensus(alnutils.aa_frequencies(aln))
          def is_majority_gap(col):
              return (float(col.count('-')) / len(col) >= gap_threshold)
          # ENH (alternatively/additionally): does any aa occur more than once?
          # ENH: choose gap-decisionmaking separately from col_consensus
      else:
          # Use the entropy-based, weighted algorithm
          if weights is None:
              seq_weights = alnutils.sequence_weights(aln, 'avg1')
          else:
              seq_weights = weights
          aa_frequencies = alnutils.aa_frequencies(aln, weights=seq_weights)
          col_consensus = make_entropy_col_consensus(aa_frequencies)
          def is_majority_gap(col):
              gap_count = 0.0
              for wt, char in zip(seq_weights, col):
                  if char == '-':
                      gap_count += wt
              return (gap_count / sum(seq_weights) >= gap_threshold)
  
      # Traverse the alignment, handling gaps etc.
      def col_wise_consensus(columns):
          """Calculate the consensus chars for an iterable of columns."""
          if not trim_ends:
              # Track if we're in the N-term or C-term end of the sequence
              in_left_end = True
              maybe_right_tail = []
          # prev_col = None
          # prev_char = None
          for col in columns:
              # Lowercase cols mean explicitly, "don't include in consensus"
              if all(c.islower() for c in col if c not in '.-'):
                  yield '-'
                  continue
              if any(c.islower() for c in col):
                  logging.warn('Mixed lowercase and uppercase letters in a '
                          'column: ' + ''.join(col))
                  col = map(str.upper, col)
  
              # Gap chars
              is_gap = is_majority_gap(col)
              if not trim_ends:
                  # Avoid N-terminal gaps in the consensus sequence
                  if in_left_end:
                      if not is_gap:
                          # Match -- we're no longer in the left end
                          in_left_end = False
                      is_gap = False
  
              # When to yield a gap here:
              #   -----------     ---------   ------  ----------
              #   in_left_end     trim_ends   is_gap  yield gap?
              #   -----------     ---------   ------  ----------
              #   True            True        (True)  yes
              #   True            False       (False) (no -- def. char)
              #   False           True        T/F     yes, if is_gap
              #   False           False       (T/F)   NO! use maybe_right_tail
              #   -----------     ---------   ------  ----------
  
              if is_gap and trim_ends:
                  yield '-'
                  continue
  
              # Get the consensus character, using the chosen algorithm
              cons_char = col_consensus(col)
  
              if trim_ends:
                  yield cons_char
              else:
                  # Avoid C-terminal gaps in the consensus sequence
                  if is_gap:
                      maybe_right_tail.append(cons_char)
                  else:
                      # Match -> gaps weren't the right tail; emit all gaps
                      for char in maybe_right_tail:
                          yield '-'
                      maybe_right_tail = []
                      yield cons_char
  
              # prev_col = col
              # prev_char = cons_char
  
          # Finally, if we were keeping a right (C-term) tail, emit it
          if not trim_ends:
              for char in maybe_right_tail:
                  yield char
  
      return ''.join(col_wise_consensus(zip(*aln)))