Current section

Files

Jump to
artificery lib console table.ex
Raw

lib/console/table.ex

defmodule Artificery.Console.Table do
@moduledoc """
A printer for tabular data.
"""
@doc """
Given a title, header, and rows, prints the data as a table.
Takes the same options as `format/4`.
"""
def print(title, header, rows, opts \\ []) do
IO.write(format(title, header, rows, opts))
end
@doc """
Given a title, header, and rows, formats the data as a table.
Takes an optional keyword list of options:
* `:padding` - (integer) sets the padding around columns
This function formats the data as iodata, it is up to the caller to print it.
"""
def format(title, header, rows, opts \\ []) do
padding = Keyword.get(opts, :padding, 1)
header = [header]
rows = stringify(rows)
widths = rows |> transpose() |> column_widths()
widths =
header
|> transpose()
|> column_widths()
|> Enum.with_index()
|> Enum.map(fn {c, i} -> max(c, Enum.at(widths, i)) end)
head =
header
|> pad_cells(widths, padding)
|> Enum.map(&[&1, ?\n])
head_len =
head
|> IO.iodata_to_binary()
|> String.length()
separator = [String.duplicate("-", head_len)]
tail =
rows
|> pad_cells(widths, padding)
|> Enum.map(&[&1, ?\n, separator, ?\n])
[IO.ANSI.bright(), title, IO.ANSI.reset(), ?\n, ?\n, head, separator, ?\n, tail]
end
defp stringify(rows, acc \\ [])
defp stringify([], acc), do: Enum.reverse(acc)
defp stringify([row | rest], acc), do: stringify(rest, [Enum.map(row, &to_string/1) | acc])
defp transpose(rows), do: rows |> Enum.zip() |> Enum.map(&Tuple.to_list/1)
defp column_widths(cols) do
Enum.map(cols, fn c -> c |> Enum.map(&byte_size/1) |> Enum.max() end)
end
defp pad_cells(rows, widths, padding) do
for r <- rows do
last_i = length(r) - 1
for {{val, width}, i} <- r |> Enum.zip(widths) |> Enum.with_index() do
if i == last_i do
# Don't pad last col
val
else
calculated_padding = max(width - byte_size(val) + padding, padding)
[val, String.pad_leading("", calculated_padding), ?|, ?\s]
end
end
end
end
end