Write a list of headers and a table of rows to the terminal in a nice format.

  Parameters:
  - headers: a list of strings that are the headers of the table
  - table: a list of lists, the actual data to be printed.
  - align: a string whose elements are in the set {'<', '^', '>'}. These define how the values
           printed in the cells will align. '<': left, '^': center, '>': right. This argument is
           optional and all unspecified columns will align as if '<' were passed for them.
  - lines: a tuple of line characters to print for the table: (row_sep, row_intersection, col_sep)

def table_to_string(headers, table, align="", *, lines=("-", "-+-", " | ")):
  """Write a list of headers and a table of rows to the terminal in a nice format.

  Parameters:
  - headers: a list of strings that are the headers of the table
  - table: a list of lists, the actual data to be printed.
  - align: a string whose elements are in the set {'<', '^', '>'}. These define how the values
           printed in the cells will align. '<': left, '^': center, '>': right. This argument is
           optional and all unspecified columns will align as if '<' were passed for them.
  - lines: a tuple of line characters to print for the table: (row_sep, row_intersection, col_sep)
  """
  header_separator, header_junction, row_separator = lines
  align = ("{0:<<" + str(len(headers)) + "}").format(align or "")
  all_lens = [tuple(len(c) for c in r) for r in table]

  if headers:
    all_lens.append(tuple(len(h) for h in headers))

  max_lens = [max(r[i] for r in all_lens) for i in range(len(headers))]
  col_outs = ["{{{0}: {1}{2}}}".format(i, align[i], w) for i, w in enumerate(max_lens)]
  fmt_str = row_separator.join(col_outs)
  if headers:
    yield fmt_str.format(*headers)
    yield header_junction.join((header_separator * ml for ml in max_lens))
  for row in table:
    yield fmt_str.format(*row)