content: """
  Printing tools.
  """
  
  import sys
  from typing import (
      Any,
      Callable,
      Dict,
      Iterable,
      List,
      Mapping,
      Optional,
      Sequence,
      Sized,
      Tuple,
      TypeVar,
      Union,
  )
  
  from pandas._config import get_option
  
  from pandas.core.dtypes.inference import is_sequence
  
  EscapeChars = Union[Mapping[str, str], Iterable[str]]
  _KT = TypeVar("_KT")
  _VT = TypeVar("_VT")
  
  
  def adjoin(space: int, *lists: List[str], **kwargs) -> str:
      """
      Glues together two sets of strings using the amount of space requested.
      The idea is to prettify.
  
      ----------
      space : int
          number of spaces for padding
      lists : str
          list of str which being joined
      strlen : callable
          function used to calculate the length of each str. Needed for unicode
          handling.
      justfunc : callable
          function used to justify str. Needed for unicode handling.
      """
      strlen = kwargs.pop("strlen", len)
      justfunc = kwargs.pop("justfunc", justify)
  
      out_lines = []
      newLists = []
      lengths = [max(map(strlen, x)) + space for x in lists[:-1]]
      # not the last one
      lengths.append(max(map(len, lists[-1])))
      maxLen = max(map(len, lists))
      for i, lst in enumerate(lists):
          nl = justfunc(lst, lengths[i], mode="left")
          nl.extend([" " * lengths[i]] * (maxLen - len(lst)))
          newLists.append(nl)
      toJoin = zip(*newLists)
      for lines in toJoin:
          out_lines.append("".join(lines))
      return "\n".join(out_lines)
  
  
  def justify(texts: Iterable[str], max_len: int, mode: str = "right") -> List[str]:
      """
      Perform ljust, center, rjust against string or list-like
      """
      if mode == "left":
          return [x.ljust(max_len) for x in texts]
      elif mode == "center":
          return [x.center(max_len) for x in texts]
      else:
          return [x.rjust(max_len) for x in texts]
  
  
  # Unicode consolidation
  # ---------------------
  #
  # pprinting utility functions for generating Unicode text or
  # bytes(3.x)/str(2.x) representations of objects.
  # Try to use these as much as possible rather than rolling your own.
  #
  # When to use
  # -----------
  #
  # 1) If you're writing code internal to pandas (no I/O directly involved),
  #    use pprint_thing().
  #
  #    It will always return unicode text which can handled by other
  #    parts of the package without breakage.
  #
  # 2) if you need to write something out to file, use
  #    pprint_thing_encoded(encoding).
  #
  #    If no encoding is specified, it defaults to utf-8. Since encoding pure
  #    ascii with utf-8 is a no-op you can safely use the default utf-8 if you're
  #    working with straight ascii.
  
  
  def _pprint_seq(
      seq: Sequence, _nest_lvl: int = 0, max_seq_items: Optional[int] = None, **kwds
  ) -> str:
      """
      internal. pprinter for iterables. you should probably use pprint_thing()
      rather than calling this directly.
  
      bounds length of printed sequence, depending on options
      """
      if isinstance(seq, set):
          fmt = "{{{body}}}"
      else:
          fmt = "[{body}]" if hasattr(seq, "__setitem__") else "({body})"
  
      if max_seq_items is False:
          nitems = len(seq)
      else:
          nitems = max_seq_items or get_option("max_seq_items") or len(seq)
  
      s = iter(seq)
      # handle sets, no slicing
      r = [
          pprint_thing(next(s), _nest_lvl + 1, max_seq_items=max_seq_items, **kwds)
          for i in range(min(nitems, len(seq)))
      ]
      body = ", ".join(r)
  
      if nitems < len(seq):
          body += ", ..."
      elif isinstance(seq, tuple) and len(seq) == 1:
          body += ","
  
      return fmt.format(body=body)
  
  
  def _pprint_dict(
      seq: Mapping, _nest_lvl: int = 0, max_seq_items: Optional[int] = None, **kwds
  ) -> str:
      """
      internal. pprinter for iterables. you should probably use pprint_thing()
      rather than calling this directly.
      """
      fmt = "{{{things}}}"
      pairs = []
  
      pfmt = "{key}: {val}"
  
      if max_seq_items is False:
          nitems = len(seq)
      else:
          nitems = max_seq_items or get_option("max_seq_items") or len(seq)
  
      for k, v in list(seq.items())[:nitems]:
          pairs.append(
              pfmt.format(
                  key=pprint_thing(k, _nest_lvl + 1, max_seq_items=max_seq_items, **kwds),
                  val=pprint_thing(v, _nest_lvl + 1, max_seq_items=max_seq_items, **kwds),
              )
          )
  
      if nitems < len(seq):
          return fmt.format(things=", ".join(pairs) + ", ...")
      else:
          return fmt.format(things=", ".join(pairs))
  
  
  def pprint_thing(
      thing: Any,
      _nest_lvl: int = 0,
      escape_chars: Optional[EscapeChars] = None,
      default_escapes: bool = False,
      quote_strings: bool = False,
      max_seq_items: Optional[int] = None,
  ) -> str:
      """
      This function is the sanctioned way of converting objects
      to a string representation and properly handles nested sequences.
  
      Parameters
      ----------
      thing : anything to be formatted
      _nest_lvl : internal use only. pprint_thing() is mutually-recursive
          with pprint_sequence, this argument is used to keep track of the
          current nesting level, and limit it.
      escape_chars : list or dict, optional
          Characters to escape. If a dict is passed the values are the
          replacements
      default_escapes : bool, default False
          Whether the input escape characters replaces or adds to the defaults
      max_seq_items : int or None, default None
          Pass through to other pretty printers to limit sequence printing
  
      Returns
      -------
      str
      """
  
      def as_escaped_string(
          thing: Any, escape_chars: Optional[EscapeChars] = escape_chars
      ) -> str:
          translate = {"\t": r"\t", "\n": r"\n", "\r": r"\r"}
          if isinstance(escape_chars, dict):
              if default_escapes:
                  translate.update(escape_chars)
              else:
                  translate = escape_chars
              escape_chars = list(escape_chars.keys())
          else:
              escape_chars = escape_chars or ()
  
          result = str(thing)
          for c in escape_chars:
              result = result.replace(c, translate[c])
          return result
  
      if hasattr(thing, "__next__"):
          return str(thing)
      elif isinstance(thing, dict) and _nest_lvl < get_option(
          "display.pprint_nest_depth"
      ):
          result = _pprint_dict(
              thing, _nest_lvl, quote_strings=True, max_seq_items=max_seq_items
          )
      elif is_sequence(thing) and _nest_lvl < get_option("display.pprint_nest_depth"):
          result = _pprint_seq(
              thing,
              _nest_lvl,
              escape_chars=escape_chars,
              quote_strings=quote_strings,
              max_seq_items=max_seq_items,
          )
      elif isinstance(thing, str) and quote_strings:
          result = f"'{as_escaped_string(thing)}'"
      else:
          result = as_escaped_string(thing)
  
      return result
  
  
  def pprint_thing_encoded(
      object, encoding: str = "utf-8", errors: str = "replace"
  ) -> bytes:
      value = pprint_thing(object)  # get unicode representation of object
      return value.encode(encoding, errors)
  
  
  def enable_data_resource_formatter(enable: bool) -> None:
      if "IPython" not in sys.modules:
          # definitely not in IPython
          return
      from IPython import get_ipython
  
      ip = get_ipython()
      if ip is None:
          # still not in IPython
          return
  
      formatters = ip.display_formatter.formatters
      mimetype = "application/vnd.dataresource+json"
  
      if enable:
          if mimetype not in formatters:
              # define tableschema formatter
              from IPython.core.formatters import BaseFormatter
  
              class TableSchemaFormatter(BaseFormatter):
                  print_method = "_repr_data_resource_"
                  _return_type = (dict,)
  
              # register it:
              formatters[mimetype] = TableSchemaFormatter()
          # enable it if it's been disabled:
          formatters[mimetype].enabled = True
      else:
          # unregister tableschema mime-type
          if mimetype in formatters:
              formatters[mimetype].enabled = False
  
  
  def default_pprint(thing: Any, max_seq_items: Optional[int] = None) -> str:
      return pprint_thing(
          thing,
          escape_chars=("\t", "\r", "\n"),
          quote_strings=True,
          max_seq_items=max_seq_items,
      )
  
  
  def format_object_summary(
      obj,
      formatter: Callable,
      is_justify: bool = True,
      name: Optional[str] = None,
      indent_for_name: bool = True,
      line_break_each_value: bool = False,
  ) -> str:
      """
      Return the formatted obj as a unicode string
  
      Parameters
      ----------
      obj : object
          must be iterable and support __getitem__
      formatter : callable
          string formatter for an element
      is_justify : boolean
          should justify the display
      name : name, optional
          defaults to the class name of the obj
      indent_for_name : bool, default True
          Whether subsequent lines should be indented to
          align with the name.
      line_break_each_value : bool, default False
          If True, inserts a line break for each value of ``obj``.
          If False, only break lines when the a line of values gets wider
          than the display width.
  
          .. versionadded:: 0.25.0
  
      Returns
      -------
      summary string
      """
      from pandas.io.formats.console import get_console_size
      from pandas.io.formats.format import get_adjustment
  
      display_width, _ = get_console_size()
      if display_width is None:
          display_width = get_option("display.width") or 80
      if name is None:
          name = type(obj).__name__
  
      if indent_for_name:
          name_len = len(name)
          space1 = f'\n{(" " * (name_len + 1))}'
          space2 = f'\n{(" " * (name_len + 2))}'
      else:
          space1 = "\n"
          space2 = "\n "  # space for the opening '['
  
      n = len(obj)
      if line_break_each_value:
          # If we want to vertically align on each value of obj, we need to
          # separate values by a line break and indent the values
          sep = ",\n " + " " * len(name)
      else:
          sep = ","
      max_seq_items = get_option("display.max_seq_items") or n
  
      # are we a truncated display
      is_truncated = n > max_seq_items
  
      # adj can optionally handle unicode eastern asian width
      adj = get_adjustment()
  
      def _extend_line(
          s: str, line: str, value: str, display_width: int, next_line_prefix: str
      ) -> Tuple[str, str]:
  
          if adj.len(line.rstrip()) + adj.len(value.rstrip()) >= display_width:
              s += line.rstrip()
              line = next_line_prefix
          line += value
          return s, line
  
      def best_len(values: List[str]) -> int:
          if values:
              return max(adj.len(x) for x in values)
          else:
              return 0
  
      close = ", "
  
      if n == 0:
          summary = f"[]{close}"
      elif n == 1 and not line_break_each_value:
          first = formatter(obj[0])
          summary = f"[{first}]{close}"
      elif n == 2 and not line_break_each_value:
          first = formatter(obj[0])
          last = formatter(obj[-1])
          summary = f"[{first}, {last}]{close}"
      else:
  
          if max_seq_items == 1:
              # If max_seq_items=1 show only last element
              head = []
              tail = [formatter(x) for x in obj[-1:]]
          elif n > max_seq_items:
              n = min(max_seq_items // 2, 10)
              head = [formatter(x) for x in obj[:n]]
              tail = [formatter(x) for x in obj[-n:]]
          else:
              head = []
              tail = [formatter(x) for x in obj]
  
          # adjust all values to max length if needed
          if is_justify:
              if line_break_each_value:
                  # Justify each string in the values of head and tail, so the
                  # strings will right align when head and tail are stacked
                  # vertically.
                  head, tail = _justify(head, tail)
              elif is_truncated or not (
                  len(", ".join(head)) < display_width
                  and len(", ".join(tail)) < display_width
              ):
                  # Each string in head and tail should align with each other
                  max_length = max(best_len(head), best_len(tail))
                  head = [x.rjust(max_length) for x in head]
                  tail = [x.rjust(max_length) for x in tail]
              # If we are not truncated and we are only a single
              # line, then don't justify
  
          if line_break_each_value:
              # Now head and tail are of type List[Tuple[str]]. Below we
              # convert them into List[str], so there will be one string per
              # value. Also truncate items horizontally if wider than
              # max_space
              max_space = display_width - len(space2)
              value = tail[0]
              for max_items in reversed(range(1, len(value) + 1)):
                  pprinted_seq = _pprint_seq(value, max_seq_items=max_items)
                  if len(pprinted_seq) < max_space:
                      break
              head = [_pprint_seq(x, max_seq_items=max_items) for x in head]
              tail = [_pprint_seq(x, max_seq_items=max_items) for x in tail]
  
          summary = ""
          line = space2
  
          for max_items in range(len(head)):
              word = head[max_items] + sep + " "
              summary, line = _extend_line(summary, line, word, display_width, space2)
  
          if is_truncated:
              # remove trailing space of last line
              summary += line.rstrip() + space2 + "..."
              line = space2
  
          for max_items in range(len(tail) - 1):
              word = tail[max_items] + sep + " "
              summary, line = _extend_line(summary, line, word, display_width, space2)
  
          # last value: no sep added + 1 space of width used for trailing ','
          summary, line = _extend_line(summary, line, tail[-1], display_width - 2, space2)
          summary += line
  
          # right now close is either '' or ', '
          # Now we want to include the ']', but not the maybe space.
          close = "]" + close.rstrip(" ")
          summary += close
  
          if len(summary) > (display_width) or line_break_each_value:
              summary += space1
          else:  # one row
              summary += " "
  
          # remove initial space
          summary = "[" + summary[len(space2) :]
  
      return summary
  
  
  def _justify(
      head: List[Sequence[str]], tail: List[Sequence[str]]
  ) -> Tuple[List[Tuple[str, ...]], List[Tuple[str, ...]]]:
      """
      Justify items in head and tail, so they are right-aligned when stacked.
  
      Parameters
      ----------
      head : list-like of list-likes of strings
      tail : list-like of list-likes of strings
  
      Returns
      -------
      tuple of list of tuples of strings
          Same as head and tail, but items are right aligned when stacked
          vertically.
  
      Examples
      --------
      >>> _justify([['a', 'b']], [['abc', 'abcd']])
      ([('  a', '   b')], [('abc', 'abcd')])
      """
      combined = head + tail
  
      # For each position for the sequences in ``combined``,
      # find the length of the largest string.
      max_length = [0] * len(combined[0])
      for inner_seq in combined:
          length = [len(item) for item in inner_seq]
          max_length = [max(x, y) for x, y in zip(max_length, length)]
  
      # justify each item in each list-like in head and tail using max_length
      head = [
          tuple(x.rjust(max_len) for x, max_len in zip(seq, max_length)) for seq in head
      ]
      tail = [
          tuple(x.rjust(max_len) for x, max_len in zip(seq, max_length)) for seq in tail
      ]
      # https://github.com/python/mypy/issues/4975
      # error: Incompatible return value type (got "Tuple[List[Sequence[str]],
      #  List[Sequence[str]]]", expected "Tuple[List[Tuple[str, ...]],
      #  List[Tuple[str, ...]]]")
      return head, tail  # type: ignore[return-value]
  
  
  def format_object_attrs(
      obj: Sized, include_dtype: bool = True
  ) -> List[Tuple[str, Union[str, int]]]:
      """
      Return a list of tuples of the (attr, formatted_value)
      for common attrs, including dtype, name, length
  
      Parameters
      ----------
      obj : object
          Must be sized.
      include_dtype : bool
          If False, dtype won't be in the returned list
  
      Returns
      -------
      list of 2-tuple
  
      """
      attrs: List[Tuple[str, Union[str, int]]] = []
      if hasattr(obj, "dtype") and include_dtype:
          # error: "Sized" has no attribute "dtype"
          attrs.append(("dtype", f"'{obj.dtype}'"))  # type: ignore[attr-defined]
      if getattr(obj, "name", None) is not None:
          # error: "Sized" has no attribute "name"
          attrs.append(("name", default_pprint(obj.name)))  # type: ignore[attr-defined]
      # error: "Sized" has no attribute "names"
      elif getattr(obj, "names", None) is not None and any(
          obj.names  # type: ignore[attr-defined]
      ):
          # error: "Sized" has no attribute "names"
          attrs.append(("names", default_pprint(obj.names)))  # type: ignore[attr-defined]
      max_seq_items = get_option("display.max_seq_items") or len(obj)
      if len(obj) > max_seq_items:
          attrs.append(("length", len(obj)))
      return attrs
  
  
  class PrettyDict(Dict[_KT, _VT]):
      """Dict extension to support abbreviated __repr__"""
  
      def __repr__(self) -> str:
          return pprint_thing(self)
  