-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, <<<>/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) -> <>. -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.