Current section

Files

Jump to
tabula lib tabula.ex
Raw

lib/tabula.ex

defmodule Tabula do
import Enum, only: [
concat: 2,
intersperse: 2,
join: 1,
map: 2,
max: 1,
with_index: 1,
zip: 2
]
@index "#"
@sheets \
org_mode: [
heading: " | ",
heading_border: "-+-",
row: " | ",
spacer: "-"
],
github_md: [
heading: " | ",
heading_border: " | ",
row: " | ",
spacer: "-"
]
@default_sheet :org_mode
defmacro __using__(opts) do
quote do
def print_table(rows) do
unquote(__MODULE__).print_table(rows, unquote(opts))
end
def print_table(rows, override_opts) do
unquote(__MODULE__).print_table(
rows, Keyword.merge(unquote(opts), override_opts))
end
def render_table(rows) do
unquote(__MODULE__).render_table(rows, unquote(opts))
end
def render_table(rows, override_opts) do
unquote(__MODULE__).render_table(
rows, Keyword.merge(unquote(opts), override_opts))
end
end
end
def print_table(rows, opts \\ []) do
render_table(rows, opts)
|> IO.puts
end
def render_table(rows, opts \\ []) do
cols = extract_cols(rows, opts)
_render_table(rows, cols, opts)
|> :erlang.list_to_binary
end
def max_widths(cols, rows) do
max_index = rows
|> length
|> strlen
cols
|> map(fn k ->
max([
strlen(k), max_index
| map(rows, &(Map.get(&1, k) |> strlen))
])
end)
end
defp _render_table(rows, [_|_]=cols, opts) do
widths = max_widths(cols, rows)
formatters = widths |> formatters(opts)
spacers = widths |> spacers(opts)
[ cols |> render_row(:heading, formatters, opts),
spacers |> render_row(:heading_border, formatters, opts),
rows
|> with_index
|> map(fn indexed_row ->
values(cols, indexed_row, opts)
|> render_row(:row, formatters, opts)
end) ]
end
defp extract_cols([first|_]=_rows, opts) do
case opts[:only] do
nil ->
first |> Map.keys
cols when is_list(cols) ->
cols
end
end
defp render_row(cells, style_element, formatters, opts) do
separator = style(style_element, opts)
cells
|> zip(formatters)
|> map(fn {k, f} -> f.(k) end)
|> intersperse(separator)
|> concat('\n')
end
defp render_cell(v) when is_binary(v),
do: v
defp render_cell(v) when is_number(v),
do: inspect(v)
defp render_cell(%{__struct__: _} = v) do
try do
to_string(v)
rescue
Protocol.UndefinedError ->
inspect(v)
end
end
defp render_cell(v),
do: inspect(v)
defp formatters(widths, _opts) do
map(widths, fn w ->
fn @index=cell ->
# need to rjust '#' orelse github fails to render
String.rjust(cell, w)
cell when is_binary(cell) ->
String.ljust(cell, w)
cell when is_number(cell) ->
cell |> render_cell() |> String.rjust(w)
cell ->
cell |> render_cell() |> String.ljust(w)
end
end)
end
defp spacers(widths, opts) do
map(widths, &(Stream.repeatedly(fn -> style(:spacer, opts) end))
|> Stream.take(&1)
|> join)
end
defp strlen(x), do: render_cell(x) |> String.length()
defp values(cols, {row, index}, _opts) do
cols
|> map(fn (@index) -> index+1
(col) -> Map.get(row, col)
end)
end
defp style(style, opts) do
sheet = Keyword.get(opts, :style, @default_sheet)
@sheets[sheet][style]
end
end