text: from operator import attrgetter
  from typing import List, Optional, Callable
  from iobes import SpanEncoding, Span, TokenFunction, SpanFormat, IOB, BIO, IOBES, BILOU, BMEOW
  from iobes.utils import safe_get, extract_type
  
  
  def sort_spans(spans: List[Span]) -> List[Span]:
      """Sort a list of spans ordered by where they start.
  
      The idea of ordering for spans is that the earlier in the tags the span
      appears (where the span starts) the earlier it will appear in the sorted
      list of spans.
  
      Args:
          spans: The spans to sort.
  
      Returns:
          The spans in sorted order. so the earlier in the tag sequence they start the
          earlier they are in this list.
      """
      return sorted(spans, key=attrgetter("start"))
  
  
  def make_blanks(spans: List[Span], length: Optional[int] = None, fill: str = TokenFunction.OUTSIDE) -> List[str]:
      """Create a list of outside tags that we can populate with tags generated from spans.
  
      Args:
          spans: The list of spans that will eventually be used to populate the tags.
          length: A pre-specified length for the list of empty tags.
          fill: The value that will be used to populate the list of tags.
  
      Returns:
          A list of outside tags.
      """
      length = tags_length_from_spans(spans) if length is None else length
      return [fill for _ in range(length)]
  
  
  def tags_length_from_spans(spans: List[Span]) -> int:
      """Get the length of the list of tags that would be needed to contain all the spans.
  
      To get a list of tags that are long enough to contain all the spans we need find
      the tag with the largest end index.
  
      Args:
          spans: The list of spans we need to contain
  
      Returns:
          The length of the tag list needed.
      """
      return max(spans, key=attrgetter("end")).end
  
  
  def make_tag(token_function: str, span_type: str, delimiter: str = "-") -> str:
      """Create a tag from a token function and a span type.
  
      Args:
          token_function: The token function for the tag, it is the first part of the tag.
          span_type: The type of the span this tag is part of it. It is the second part
              of the tag.
          delimiter: A separator character (or sequence of characters) that separate the
              `token_function` and the `span_type`.
  
      Returns:
          The created tag.
      """
      return f"{token_function}{delimiter}{span_type}"
  
  
  def write_tags(spans: List[Span], span_type: SpanEncoding, length: Optional[int] = None):
      tags = make_blanks(spans, length)
      if span_type is SpanEncoding.IOB:
          return write_iob_tags(span, length)
      if span_type is SpanEncoding.BIO:
          return _write_tags(spans, BIO, tags)
      if span_type is SpanEncoding.IOBES:
          return _write_tags(spans, IOBES, tags)
      if span_type is SpanEncoding.BILOU:
          return _write_tags(spans, BILOU, tags)
      if span_type is SpanEncoding.BMEOW or span_type is SpanEncoding.BMEWO:
          return _write_tags(spans, BMEOW, tags)
      if span_type is SpanEncoding.TOKEN:
          return _write_tags(spans, TOKEN, tags)
      raise ValueError(f"Unknown SpanEncoding scheme, got: `{span_type}`")
  
  
  def write_iob_tags(spans: Span, tags: Optional[List[str]] = None, length: Optional[int] = None) -> List[str]:
      """This is a special case because the IOB tags are contextual."""
      spans = sort_spans(spans)
      tags = make_blanks(spans, length) if tags is None else tags
      if not spans:
          return tags
      # The first span can never start with a `B` because it is first and therefore can't follow a
      # span of the same type.
      for token in spans[0].tokens:
          tags[token] = f"{TokenFunction.INSIDE}-{spans[0].type}"
      for prev, span in zip(spans, spans[1:]):
          for token in span.tokens:
              tags[token] = f"{TokenFunction.INSIDE}-{span.type}"
          if prev.end == span.start and prev.type == span.type:
              tags[span.start] = f"{TokenFunction.BEGIN}-{span.type}"
      return tags
  
  
  def _write_tags(spans: Span, span_format: SpanFormat, tags: Optional[List[str]] = None, length: Optional[int] = None):
      spans = sort_spans(spans)
      tags = make_blanks(spans, length) if tags is None else tags
      for span in spans:
          if span.end == span.start + 1:
              tags[span.start] = make_tag(span_format.SINGLE, span.type)
              continue
          tags[span.start] = make_tag(span_format.BEGIN, span.type)
          tags[span.end - 1] = make_tag(span_format.END, span.type)
          for token in span.tokens[1:-1]:
              tags[token] = make_tag(span_format.INSIDE, span.type)
      return tags
  
  
  def write_bio_tags(spans: Span, tags: Optional[List[str]] = None, length: Optional[int] = None) -> List[str]:
      return _write_tags(spans, BIO, tags, length)
  
  
  def write_iobes_tags(spans: Span, tags: Optional[List[str]] = None, length: Optional[int] = None) -> List[str]:
      return _write_tags(spans, IOBES, tags, length)
  
  
  def write_bilou_tags(spans: Span, tags: Optional[List[str]] = None, length: Optional[int] = None) -> List[str]:
      return _write_tags(spans, BILOU, tags, length)
  
  
  def write_bmeow_tags(spans: Span, tags: Optional[List[str]] = None, length: Optional[int] = None) -> List[str]:
      return _write_tags(spans, BMEOW, tags, length)
  
  
  write_bmewo_tags = write_bmeow_tags
  