-module(spruce@table).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/spruce/table.gleam").
-export([new/0, headers/2, rows/2, style_fn/2, width/2, column_widths/2, border/2, row_separators/2, render/2]).
-export_type([table/0, grid_chars/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(" Bordered, ANSI-aware data table rendering.\n").
-opaque table() :: {table,
list(binary()),
list(list(binary())),
gleam@option:option(fun((integer(), integer()) -> spruce@style:style())),
gleam@option:option(integer()),
gleam@option:option(list(integer())),
spruce@box:border(),
boolean()}.
-type grid_chars() :: {grid_chars,
spruce@box:border_chars(),
binary(),
binary(),
binary(),
binary(),
binary(),
binary(),
binary(),
binary()}.
-file("src/spruce/table.gleam", 43).
?DOC(" Create an empty table.\n").
-spec new() -> table().
new() ->
{table, [], [], none, none, none, normal, false}.
-file("src/spruce/table.gleam", 56).
?DOC(" Set the optional header row.\n").
-spec headers(table(), list(binary())) -> table().
headers(Table, Headers) ->
{table,
Headers,
erlang:element(3, Table),
erlang:element(4, Table),
erlang:element(5, Table),
erlang:element(6, Table),
erlang:element(7, Table),
erlang:element(8, Table)}.
-file("src/spruce/table.gleam", 61).
?DOC(" Set the body rows. Short rows are padded with empty cells at render time.\n").
-spec rows(table(), list(list(binary()))) -> table().
rows(Table, Rows) ->
{table,
erlang:element(2, Table),
Rows,
erlang:element(4, Table),
erlang:element(5, Table),
erlang:element(6, Table),
erlang:element(7, Table),
erlang:element(8, Table)}.
-file("src/spruce/table.gleam", 68).
?DOC(
" Set a per-cell style function.\n"
"\n"
" Body rows are passed zero-based row indexes. Header cells are passed row `-1`.\n"
).
-spec style_fn(table(), fun((integer(), integer()) -> spruce@style:style())) -> table().
style_fn(Table, Style_fn) ->
{table,
erlang:element(2, Table),
erlang:element(3, Table),
{some, Style_fn},
erlang:element(5, Table),
erlang:element(6, Table),
erlang:element(7, Table),
erlang:element(8, Table)}.
-file("src/spruce/table.gleam", 668).
-spec non_negative(integer()) -> integer().
non_negative(Value) ->
gleam@int:max(0, Value).
-file("src/spruce/table.gleam", 76).
?DOC(
" Constrain the overall table to a maximum visual width.\n"
"\n"
" The available cell content width is distributed evenly across columns. Cells\n"
" that exceed their column width are wrapped with `spruce/align.wrap`.\n"
).
-spec width(table(), integer()) -> table().
width(Table, Width) ->
{table,
erlang:element(2, Table),
erlang:element(3, Table),
erlang:element(4, Table),
{some, non_negative(Width)},
none,
erlang:element(7, Table),
erlang:element(8, Table)}.
-file("src/spruce/table.gleam", 84).
?DOC(
" Constrain columns to maximum visual widths.\n"
"\n"
" Widths less than one are ignored. Columns without a corresponding configured\n"
" width use their natural content width.\n"
).
-spec column_widths(table(), list(integer())) -> table().
column_widths(Table, Widths) ->
{table,
erlang:element(2, Table),
erlang:element(3, Table),
erlang:element(4, Table),
none,
{some, Widths},
erlang:element(7, Table),
erlang:element(8, Table)}.
-file("src/spruce/table.gleam", 92).
?DOC(
" Set the table border style.\n"
"\n"
" Junctions are exact for Normal, Rounded, Thick, and Double. Other border\n"
" styles approximate junctions from their edge characters.\n"
).
-spec border(table(), spruce@box:border()) -> table().
border(Table, Border) ->
{table,
erlang:element(2, Table),
erlang:element(3, Table),
erlang:element(4, Table),
erlang:element(5, Table),
erlang:element(6, Table),
Border,
erlang:element(8, Table)}.
-file("src/spruce/table.gleam", 97).
?DOC(" Toggle separator lines between body rows.\n").
-spec row_separators(table(), boolean()) -> table().
row_separators(Table, Enabled) ->
{table,
erlang:element(2, Table),
erlang:element(3, Table),
erlang:element(4, Table),
erlang:element(5, Table),
erlang:element(6, Table),
erlang:element(7, Table),
Enabled}.
-file("src/spruce/table.gleam", 502).
-spec render_separator_cells(list(integer()), binary(), binary(), binary()) -> binary().
render_separator_cells(Widths, Mid, Right, Horizontal) ->
case Widths of
[] ->
<<""/utf8>>;
[Width | Rest] ->
Segment = gleam@string:repeat(Horizontal, Width + 2),
Separator = case Rest of
[] ->
Right;
_ ->
Mid
end,
<<<<Segment/binary, Separator/binary>>/binary,
(render_separator_cells(Rest, Mid, Right, Horizontal))/binary>>
end.
-file("src/spruce/table.gleam", 492).
-spec render_separator(list(integer()), binary(), binary(), binary(), binary()) -> binary().
render_separator(Widths, Left, Mid, Right, Horizontal) ->
<<Left/binary,
(render_separator_cells(Widths, Mid, Right, Horizontal))/binary>>.
-file("src/spruce/table.gleam", 482).
-spec render_bottom(list(integer()), grid_chars()) -> binary().
render_bottom(Widths, Chars) ->
render_separator(
Widths,
erlang:element(8, erlang:element(2, Chars)),
erlang:element(10, Chars),
erlang:element(6, erlang:element(2, Chars)),
erlang:element(7, erlang:element(2, Chars))
).
-file("src/spruce/table.gleam", 462).
-spec render_header_separator(list(integer()), grid_chars()) -> binary().
render_header_separator(Widths, Chars) ->
render_separator(
Widths,
erlang:element(4, Chars),
erlang:element(5, Chars),
erlang:element(6, Chars),
erlang:element(3, erlang:element(2, Chars))
).
-file("src/spruce/table.gleam", 444).
-spec line_at(list(binary()), integer()) -> binary().
line_at(Lines, Index) ->
case {Lines, Index} of
{[], _} ->
<<""/utf8>>;
{[Line | _], 0} ->
Line;
{[_ | Rest], _} ->
line_at(Rest, Index - 1)
end.
-file("src/spruce/table.gleam", 436).
-spec cell_lines_at(list(list(binary())), integer()) -> list(binary()).
cell_lines_at(Cells, Index) ->
case {Cells, Index} of
{[], _} ->
[];
{[Lines | _], 0} ->
Lines;
{[_ | Rest], _} ->
cell_lines_at(Rest, Index - 1)
end.
-file("src/spruce/table.gleam", 386).
-spec render_cell_line(
list(list(binary())),
list(integer()),
binary(),
integer(),
integer()
) -> binary().
render_cell_line(Cells, Widths, Separator, Line_index, Col_index) ->
case Widths of
[] ->
<<""/utf8>>;
[Width | Rest] ->
Content = begin
_pipe = cell_lines_at(Cells, Col_index),
_pipe@1 = line_at(_pipe, Line_index),
spruce@align:pad_right(_pipe@1, Width)
end,
Inner_separator = case Rest of
[] ->
<<""/utf8>>;
_ ->
Separator
end,
<<<<<<<<" "/utf8, Content/binary>>/binary, " "/utf8>>/binary,
Inner_separator/binary>>/binary,
(render_cell_line(
Cells,
Rest,
Separator,
Line_index,
Col_index + 1
))/binary>>
end.
-file("src/spruce/table.gleam", 365).
-spec render_row_lines(
list(list(binary())),
list(integer()),
grid_chars(),
integer(),
integer(),
list(binary())
) -> list(binary()).
render_row_lines(Cells, Widths, Chars, Height, Index, Acc) ->
case Index >= Height of
true ->
lists:reverse(Acc);
false ->
Line = <<<<(erlang:element(9, erlang:element(2, Chars)))/binary,
(render_cell_line(
Cells,
Widths,
erlang:element(5, erlang:element(2, Chars)),
Index,
0
))/binary>>/binary,
(erlang:element(5, erlang:element(2, Chars)))/binary>>,
render_row_lines(
Cells,
Widths,
Chars,
Height,
Index + 1,
[Line | Acc]
)
end.
-file("src/spruce/table.gleam", 359).
-spec max_cell_height(list(list(binary())), integer()) -> integer().
max_cell_height(Cells, Minimum) ->
_pipe = Cells,
_pipe@1 = gleam@list:map(_pipe, fun erlang:length/1),
gleam@list:fold(_pipe@1, Minimum, fun gleam@int:max/2).
-file("src/spruce/table.gleam", 415).
-spec apply_style(
binary(),
spruce:spruce(),
gleam@option:option(fun((integer(), integer()) -> spruce@style:style())),
integer(),
integer()
) -> binary().
apply_style(Content, Sp, Maybe_style, Row_index, Col_index) ->
case Maybe_style of
none ->
Content;
{some, Style_fn} ->
spruce@style:render(Sp, Style_fn(Row_index, Col_index), Content)
end.
-file("src/spruce/table.gleam", 354).
-spec wrap_cell(binary(), integer()) -> binary().
wrap_cell(Cell, Width) ->
gleam@bool:guard(
Width =< 0,
Cell,
fun() -> spruce@align:wrap(Cell, Width) end
).
-file("src/spruce/table.gleam", 428).
-spec cell_at(list(binary()), integer()) -> binary().
cell_at(Row, Index) ->
case {Row, Index} of
{[], _} ->
<<""/utf8>>;
{[Cell | _], 0} ->
Cell;
{[_ | Rest], _} ->
cell_at(Rest, Index - 1)
end.
-file("src/spruce/table.gleam", 322).
-spec render_cell_lines(
spruce:spruce(),
list(binary()),
list(integer()),
gleam@option:option(fun((integer(), integer()) -> spruce@style:style())),
integer(),
integer()
) -> list(list(binary())).
render_cell_lines(Sp, Row, Widths, Maybe_style, Row_index, Col_index) ->
case Widths of
[] ->
[];
[Width | Rest] ->
Lines = begin
_pipe = cell_at(Row, Col_index),
_pipe@1 = wrap_cell(_pipe, Width),
_pipe@2 = gleam@string:split(_pipe@1, <<"\n"/utf8>>),
gleam@list:map(
_pipe@2,
fun(_capture) ->
apply_style(
_capture,
Sp,
Maybe_style,
Row_index,
Col_index
)
end
)
end,
[Lines |
render_cell_lines(
Sp,
Row,
Rest,
Maybe_style,
Row_index,
Col_index + 1
)]
end.
-file("src/spruce/table.gleam", 308).
-spec render_row(
spruce:spruce(),
list(binary()),
list(integer()),
gleam@option:option(fun((integer(), integer()) -> spruce@style:style())),
integer(),
grid_chars()
) -> list(binary()).
render_row(Sp, Row, Widths, Maybe_style, Row_index, Chars) ->
Cells = render_cell_lines(Sp, Row, Widths, Maybe_style, Row_index, 0),
Height = max_cell_height(Cells, 1),
render_row_lines(Cells, Widths, Chars, Height, 0, []).
-file("src/spruce/table.gleam", 252).
-spec render_header(
spruce:spruce(),
list(binary()),
list(integer()),
gleam@option:option(fun((integer(), integer()) -> spruce@style:style())),
grid_chars(),
list(binary())
) -> list(binary()).
render_header(Sp, Headers, Widths, Maybe_style, Chars, Body) ->
case Headers of
[] ->
[];
_ ->
Header = render_row(Sp, Headers, Widths, Maybe_style, -1, Chars),
case Body of
[] ->
Header;
_ ->
_pipe = Header,
lists:append(
_pipe,
[render_header_separator(Widths, Chars)]
)
end
end.
-file("src/spruce/table.gleam", 452).
-spec render_top(list(integer()), grid_chars()) -> binary().
render_top(Widths, Chars) ->
render_separator(
Widths,
erlang:element(2, erlang:element(2, Chars)),
erlang:element(3, Chars),
erlang:element(4, erlang:element(2, Chars)),
erlang:element(3, erlang:element(2, Chars))
).
-file("src/spruce/table.gleam", 472).
-spec render_row_separator(list(integer()), grid_chars()) -> binary().
render_row_separator(Widths, Chars) ->
render_separator(
Widths,
erlang:element(7, Chars),
erlang:element(8, Chars),
erlang:element(9, Chars),
erlang:element(3, erlang:element(2, Chars))
).
-file("src/spruce/table.gleam", 273).
-spec render_body_rows(
spruce:spruce(),
list(list(binary())),
list(integer()),
gleam@option:option(fun((integer(), integer()) -> spruce@style:style())),
grid_chars(),
boolean(),
integer()
) -> list(binary()).
render_body_rows(
Sp,
Rows,
Widths,
Maybe_style,
Chars,
Row_separators,
Row_index
) ->
case Rows of
[] ->
[];
[Row | Rest] ->
Rendered = render_row(
Sp,
Row,
Widths,
Maybe_style,
Row_index,
Chars
),
Tail = render_body_rows(
Sp,
Rest,
Widths,
Maybe_style,
Chars,
Row_separators,
Row_index + 1
),
case {Row_separators, Rest} of
{true, [_ | _]} ->
_pipe = Rendered,
_pipe@1 = lists:append(
_pipe,
[render_row_separator(Widths, Chars)]
),
lists:append(_pipe@1, Tail);
{_, _} ->
_pipe@2 = Rendered,
lists:append(_pipe@2, Tail)
end
end.
-file("src/spruce/table.gleam", 654).
-spec custom_grid_chars(spruce@box:border_chars()) -> grid_chars().
custom_grid_chars(Chars) ->
{grid_chars,
Chars,
erlang:element(3, Chars),
erlang:element(9, Chars),
erlang:element(3, Chars),
erlang:element(5, Chars),
erlang:element(9, Chars),
erlang:element(3, Chars),
erlang:element(5, Chars),
erlang:element(7, Chars)}.
-file("src/spruce/table.gleam", 641).
-spec hidden_grid_chars() -> grid_chars().
hidden_grid_chars() ->
custom_grid_chars(
{border_chars,
<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>}
).
-file("src/spruce/table.gleam", 628).
-spec block_grid_chars() -> grid_chars().
block_grid_chars() ->
custom_grid_chars(
{border_chars,
<<"█"/utf8>>,
<<"█"/utf8>>,
<<"█"/utf8>>,
<<"█"/utf8>>,
<<"█"/utf8>>,
<<"█"/utf8>>,
<<"█"/utf8>>,
<<"█"/utf8>>}
).
-file("src/spruce/table.gleam", 605).
-spec double_grid_chars() -> grid_chars().
double_grid_chars() ->
{grid_chars,
{border_chars,
<<"╔"/utf8>>,
<<"═"/utf8>>,
<<"╗"/utf8>>,
<<"║"/utf8>>,
<<"╝"/utf8>>,
<<"═"/utf8>>,
<<"╚"/utf8>>,
<<"║"/utf8>>},
<<"╦"/utf8>>,
<<"╠"/utf8>>,
<<"╬"/utf8>>,
<<"╣"/utf8>>,
<<"╠"/utf8>>,
<<"╬"/utf8>>,
<<"╣"/utf8>>,
<<"╩"/utf8>>}.
-file("src/spruce/table.gleam", 582).
-spec thick_grid_chars() -> grid_chars().
thick_grid_chars() ->
{grid_chars,
{border_chars,
<<"┏"/utf8>>,
<<"━"/utf8>>,
<<"┓"/utf8>>,
<<"┃"/utf8>>,
<<"┛"/utf8>>,
<<"━"/utf8>>,
<<"┗"/utf8>>,
<<"┃"/utf8>>},
<<"┳"/utf8>>,
<<"┣"/utf8>>,
<<"╋"/utf8>>,
<<"┫"/utf8>>,
<<"┣"/utf8>>,
<<"╋"/utf8>>,
<<"┫"/utf8>>,
<<"┻"/utf8>>}.
-file("src/spruce/table.gleam", 559).
-spec rounded_grid_chars() -> grid_chars().
rounded_grid_chars() ->
{grid_chars,
{border_chars,
<<"╭"/utf8>>,
<<"─"/utf8>>,
<<"╮"/utf8>>,
<<"│"/utf8>>,
<<"╯"/utf8>>,
<<"─"/utf8>>,
<<"╰"/utf8>>,
<<"│"/utf8>>},
<<"┬"/utf8>>,
<<"├"/utf8>>,
<<"┼"/utf8>>,
<<"┤"/utf8>>,
<<"├"/utf8>>,
<<"┼"/utf8>>,
<<"┤"/utf8>>,
<<"┴"/utf8>>}.
-file("src/spruce/table.gleam", 536).
-spec normal_grid_chars() -> grid_chars().
normal_grid_chars() ->
{grid_chars,
{border_chars,
<<"┌"/utf8>>,
<<"─"/utf8>>,
<<"┐"/utf8>>,
<<"│"/utf8>>,
<<"┘"/utf8>>,
<<"─"/utf8>>,
<<"└"/utf8>>,
<<"│"/utf8>>},
<<"┬"/utf8>>,
<<"├"/utf8>>,
<<"┼"/utf8>>,
<<"┤"/utf8>>,
<<"├"/utf8>>,
<<"┼"/utf8>>,
<<"┤"/utf8>>,
<<"┴"/utf8>>}.
-file("src/spruce/table.gleam", 524).
-spec grid_chars(spruce@box:border()) -> grid_chars().
grid_chars(Border) ->
case Border of
normal ->
normal_grid_chars();
rounded ->
rounded_grid_chars();
thick ->
thick_grid_chars();
double ->
double_grid_chars();
block ->
block_grid_chars();
hidden ->
hidden_grid_chars();
{custom, Chars} ->
custom_grid_chars(Chars)
end.
-file("src/spruce/table.gleam", 231).
-spec distributed_widths_loop(integer(), integer(), integer(), list(integer())) -> list(integer()).
distributed_widths_loop(Remaining, Base, Extra, Acc) ->
case Remaining of
0 ->
lists:reverse(Acc);
_ ->
Add = case Extra > 0 of
true ->
1;
false ->
0
end,
distributed_widths_loop(
Remaining - 1,
Base,
Extra - Add,
[Base + Add | Acc]
)
end.
-file("src/spruce/table.gleam", 223).
-spec distributed_widths(integer(), integer()) -> list(integer()).
distributed_widths(Column_count, Table_width) ->
Total_cell_width = gleam@int:max(1, Table_width - ((Column_count * 3) + 1)),
Base = gleam@int:max(1, case Column_count of
0 -> 0;
Gleam@denominator -> Total_cell_width div Gleam@denominator
end),
Extra = Total_cell_width - (Base * Column_count),
distributed_widths_loop(Column_count, Base, Extra, []).
-file("src/spruce/table.gleam", 211).
-spec cap_widths(list(integer()), list(integer())) -> list(integer()).
cap_widths(Natural, Caps) ->
case {Natural, Caps} of
{[], _} ->
[];
{[Width | Rest], []} ->
[Width | cap_widths(Rest, [])];
{[Width@1 | Rest@1], [Cap | Cap_rest]} when Cap > 0 ->
[gleam@int:min(Width@1, Cap) | cap_widths(Rest@1, Cap_rest)];
{[Width@2 | Rest@2], [_ | Cap_rest@1]} ->
[Width@2 | cap_widths(Rest@2, Cap_rest@1)]
end.
-file("src/spruce/table.gleam", 206).
-spec cell_width(binary()) -> integer().
cell_width(Cell) ->
{Width, _} = spruce@align:size(Cell),
Width.
-file("src/spruce/table.gleam", 162).
-spec column_widths_loop(
integer(),
integer(),
list(binary()),
list(list(binary())),
list(integer())
) -> list(integer()).
column_widths_loop(Index, Column_count, Headers, Rows, Acc) ->
case Index >= Column_count of
true ->
lists:reverse(Acc);
false ->
Header_width = begin
_pipe = cell_at(Headers, Index),
cell_width(_pipe)
end,
Row_width = begin
_pipe@1 = Rows,
_pipe@3 = gleam@list:map(
_pipe@1,
fun(Row) -> _pipe@2 = cell_at(Row, Index),
cell_width(_pipe@2) end
),
gleam@list:fold(_pipe@3, Header_width, fun gleam@int:max/2)
end,
column_widths_loop(
Index + 1,
Column_count,
Headers,
Rows,
[Row_width | Acc]
)
end.
-file("src/spruce/table.gleam", 154).
-spec natural_column_widths(integer(), list(binary()), list(list(binary()))) -> list(integer()).
natural_column_widths(Column_count, Headers, Rows) ->
column_widths_loop(0, Column_count, Headers, Rows, []).
-file("src/spruce/table.gleam", 186).
-spec resolved_column_widths(
integer(),
list(binary()),
list(list(binary())),
gleam@option:option(integer()),
gleam@option:option(list(integer()))
) -> list(integer()).
resolved_column_widths(Column_count, Headers, Rows, Table_width, Max_widths) ->
Natural = natural_column_widths(Column_count, Headers, Rows),
case Max_widths of
{some, Widths} ->
cap_widths(Natural, Widths);
none ->
case Table_width of
{some, Width} ->
cap_widths(Natural, distributed_widths(Column_count, Width));
none ->
Natural
end
end.
-file("src/spruce/table.gleam", 148).
-spec count_columns(list(binary()), list(list(binary()))) -> integer().
count_columns(Headers, Rows) ->
_pipe = Rows,
_pipe@1 = gleam@list:map(_pipe, fun erlang:length/1),
gleam@list:fold(_pipe@1, erlang:length(Headers), fun gleam@int:max/2).
-file("src/spruce/table.gleam", 102).
?DOC(" Render a table as a bordered grid.\n").
-spec render(spruce:spruce(), table()) -> binary().
render(Sp, Table) ->
Column_count = count_columns(
erlang:element(2, Table),
erlang:element(3, Table)
),
case Column_count of
0 ->
<<""/utf8>>;
_ ->
Widths = resolved_column_widths(
Column_count,
erlang:element(2, Table),
erlang:element(3, Table),
erlang:element(5, Table),
erlang:element(6, Table)
),
Chars = grid_chars(erlang:element(7, Table)),
Body = render_body_rows(
Sp,
erlang:element(3, Table),
Widths,
erlang:element(4, Table),
Chars,
erlang:element(8, Table),
0
),
Lines = begin
_pipe = [render_top(Widths, Chars)],
_pipe@1 = lists:append(
_pipe,
render_header(
Sp,
erlang:element(2, Table),
Widths,
erlang:element(4, Table),
Chars,
Body
)
),
_pipe@2 = lists:append(_pipe@1, Body),
lists:append(_pipe@2, [render_bottom(Widths, Chars)])
end,
_pipe@3 = Lines,
_pipe@4 = gleam@list:map(
_pipe@3,
fun(Line) ->
<<(spruce@internal@layout:indent_prefix(Sp))/binary,
Line/binary>>
end
),
gleam@string:join(_pipe@4, <<"\n"/utf8>>)
end.