Current section

Files

Jump to
table lib table.ex
Raw

lib/table.ex

defmodule Table do
@styles %{plain: %{top: ~w[+- -+- -+],
middle: ~w[+- -+- -+],
bottom: ~w[+- -+- -+],
dash: ?-,
header_walls: ["| ", " | ", " |"],
body_walls: ["| ", " | ", " |"]},
unicode: %{top: ~w[┌─ ─┬─ ─┐],
middle: ~w[├─ ─┼─ ─┤],
bottom: ~w[└─ ─┴─ ─┘],
dash: ?─,
header_walls: ["│ ", " │ ", " │"],
body_walls: ["│ ", " ╎ ", " │"]}}
defp row(cells, walls, pad \\ ?\s) do
[first, mid, last] = walls
line = cells |> Enum.map(fn({content, width})-> String.ljust(content, width, pad) end)
|> Enum.join(mid)
"#{first}#{line}#{last}"
end
defp compute_size(rows) do
rows |> Enum.map(fn(row)-> Enum.map(row, &String.length/1) end)
|> Enum.reduce(fn(row, acc)-> Enum.map(Enum.zip(row, acc), fn({a, b})-> max(a, b) end) end)
end
defp force_string(style, thing) do
case thing do
x when is_bitstring(x) -> x
x when is_list(x) -> table x, style, false
x when is_map(x) -> table x, style, false
x -> inspect(x)
end
end
defp ensure_string(style, rows) do
Enum.map(rows, fn(row)-> Enum.map(row, &(force_string style, &1)) end)
end
defp matrix(body, border?, style) do
body = ensure_string(style, body)
style = @styles[style]
sizes = compute_size(body)
empty = Enum.zip(Enum.map(sizes, fn(_)-> "" end), sizes)
body = Enum.map_join(body, "\n", fn(x)-> row(Enum.zip(x, sizes), style[:body_walls]) end)
cond do
border? ->
top = row(empty, style[:top], style[:dash])
bottom = row(empty, style[:bottom], style[:dash])
"#{top}\n#{body}\n#{bottom}"
true -> body
end
end
defp matrix(header, body, border?, style) do
header = Enum.map(header, &(force_string style, &1))
body = ensure_string(style, body)
style = @styles[style]
sizes = compute_size([header] ++ body)
empty = Enum.zip(Enum.map(sizes, fn(_)-> "" end), sizes)
"""
#{row(empty, style[:top], style[:dash])}
#{row(Enum.zip(header, sizes), style[:header_walls])}
#{row(empty, style[:middle], style[:dash])}
#{Enum.map_join(body, "\n", fn(x)-> row(Enum.zip(x, sizes), style[:body_walls]) end)}
#{row(empty, style[:bottom], style[:dash])}
"""
end
@doc """
Supported types
* map
* list
* list of map
* list of list
Examples
iex> IO.write Table.table(%{"key"=> "value", "more"=> "more val"})
+------+----------+
| key | value |
| more | more val |
+------+----------+
iex> IO.write Table.table(["list", "is", "vertical"])
+----------+
| list |
| is |
| vertical |
+----------+
iex> IO.write Table.table([%{"style"=> :ascii},
%{"style"=> :unicode}], :unicode)
┌──────────┐
│ style │
├──────────┤
│ :ascii │
│ :unicode │
└──────────┘
IO.write Table.table(%{"style"=> %{"level" => "aha"}, "styl"=> :unicode})
"""
def table(data, style \\ :plain, border? \\ true) do
cond do
is_map(data) ->
keys = Dict.keys(data)
values = Enum.map keys, fn(x)-> Dict.get(data, x) end
cond do
Dict.size(data) == 0 -> ""
Enum.all?(values, &is_list/1) -> matrix(keys, values, border?, style)
true -> Enum.zip(keys, values) |> Enum.map(&Tuple.to_list/1) |> matrix(border?, style)
end
is_list(data) ->
cond do
Enum.count(data) == 0 -> ""
Enum.all?(data, &is_list/1) -> matrix(data, border?, style)
Enum.all?(data, &is_map/1) ->
header = Dict.keys List.first data
data = Enum.map(data, fn(row)-> Enum.map(header, &(Dict.get(row, &1))) end)
matrix(header, data, border?, style)
true -> data |> Enum.map(fn(x)-> [x] end) |> matrix(border?, style)
end
end
end
end