Skip to main content

src/spruce@table.erl

-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.