Skip to main content

src/spruce@list.erl

-module(spruce@list).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/spruce/list.gleam").
-export([new/0, item/2, child/3, nested/3, kind/2, enumerator/2, render/2]).
-export_type([enumerator/0, items/0, item/0, kind/0, list_/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(
    " Pure bullet and ordered list rendering.\n"
    "\n"
    " Lists are rendered with an explicit `Spruce` context. Bullet lists use a\n"
    " Unicode bullet when color is supported and a deterministic ASCII marker when\n"
    " it is not. Ordered lists count from one at each nesting depth.\n"
).

-type enumerator() :: auto | {custom, fun((integer(), integer()) -> binary())}.

-type items() :: empty | {cons, item(), items()}.

-type item() :: {item, binary(), items()}.

-type kind() :: bullet | ordered.

-opaque list_() :: {list, items(), kind(), enumerator()}.

-file("src/spruce/list.gleam", 40).
?DOC(" Build an empty bullet list.\n").
-spec new() -> list_().
new() ->
    {list, empty, bullet, auto}.

-file("src/spruce/list.gleam", 161).
-spec append_item(items(), item()) -> items().
append_item(Items, Item) ->
    case Items of
        empty ->
            {cons, Item, empty};

        {cons, First, Rest} ->
            {cons, First, append_item(Rest, Item)}
    end.

-file("src/spruce/list.gleam", 45).
?DOC(" Add a top-level item, preserving insertion order.\n").
-spec item(list_(), binary()) -> list_().
item(List_, Label) ->
    {list,
        append_item(erlang:element(2, List_), {item, Label, empty}),
        erlang:element(3, List_),
        erlang:element(4, List_)}.

-file("src/spruce/list.gleam", 168).
-spec labels_to_items(list(binary())) -> items().
labels_to_items(Labels) ->
    case Labels of
        [] ->
            empty;

        [First | Rest] ->
            {cons, {item, First, empty}, labels_to_items(Rest)}
    end.

-file("src/spruce/list.gleam", 53).
?DOC(" Add a top-level item with one level of child labels.\n").
-spec child(list_(), binary(), list(binary())) -> list_().
child(List_, Label, Children) ->
    {list,
        append_item(
            erlang:element(2, List_),
            {item, Label, labels_to_items(Children)}
        ),
        erlang:element(3, List_),
        erlang:element(4, List_)}.

-file("src/spruce/list.gleam", 67).
?DOC(
    " Add a top-level item whose children come from another list.\n"
    "\n"
    " The nested list's item tree is preserved, while the parent list's kind and\n"
    " enumerator control rendering at every depth.\n"
).
-spec nested(list_(), binary(), list_()) -> list_().
nested(List_, Label, Children) ->
    {list,
        append_item(
            erlang:element(2, List_),
            {item, Label, erlang:element(2, Children)}
        ),
        erlang:element(3, List_),
        erlang:element(4, List_)}.

-file("src/spruce/list.gleam", 78).
?DOC(" Set the default marker style for the list.\n").
-spec kind(list_(), kind()) -> list_().
kind(List_, Kind_) ->
    {list, erlang:element(2, List_), Kind_, erlang:element(4, List_)}.

-file("src/spruce/list.gleam", 87).
?DOC(
    " Set a custom enumerator.\n"
    "\n"
    " The function receives the one-based item index within the current depth and\n"
    " the one-based depth of the item being rendered. It should return the complete\n"
    " marker to place before that item's first label line.\n"
).
-spec enumerator(list_(), fun((integer(), integer()) -> binary())) -> list_().
enumerator(List_, Enumerate) ->
    {list,
        erlang:element(2, List_),
        erlang:element(3, List_),
        {custom, Enumerate}}.

-file("src/spruce/list.gleam", 151).
-spec render_label(binary(), binary(), binary()) -> list(binary()).
render_label(Prefix, Follow, Label) ->
    case gleam@string:split(Label, <<"\n"/utf8>>) of
        [] ->
            [Prefix];

        [First | Rest] ->
            [<<Prefix/binary, First/binary>> |
                gleam@list:map(
                    Rest,
                    fun(Line) -> <<Follow/binary, Line/binary>> end
                )]
    end.

-file("src/spruce/list.gleam", 193).
-spec bullet_marker(spruce:spruce()) -> binary().
bullet_marker(Sp) ->
    gleam@bool:guard(
        not spruce:supports_color(Sp),
        <<"- "/utf8>>,
        fun() -> <<"• "/utf8>> end
    ).

-file("src/spruce/list.gleam", 176).
-spec marker(spruce:spruce(), kind(), enumerator(), integer(), integer()) -> binary().
marker(Sp, Kind, Enumerator, Index, Depth) ->
    case Enumerator of
        {custom, Enumerate} ->
            Enumerate(Index, Depth);

        auto ->
            case Kind of
                bullet ->
                    bullet_marker(Sp);

                ordered ->
                    <<(erlang:integer_to_binary(Index))/binary, ". "/utf8>>
            end
    end.

-file("src/spruce/list.gleam", 124).
-spec render_item(
    spruce:spruce(),
    item(),
    kind(),
    enumerator(),
    binary(),
    integer(),
    integer()
) -> list(binary()).
render_item(Sp, Item, Kind, Enumerator, Base, Depth, Index) ->
    Prefix = <<<<Base/binary,
            (gleam@string:repeat(<<"  "/utf8>>, Depth - 1))/binary>>/binary,
        (marker(Sp, Kind, Enumerator, Index, Depth))/binary>>,
    Follow = gleam@string:repeat(
        <<" "/utf8>>,
        spruce@align:visual_length(Prefix)
    ),
    _pipe = render_label(Prefix, Follow, erlang:element(2, Item)),
    lists:append(
        _pipe,
        render_items(
            Sp,
            erlang:element(3, Item),
            Kind,
            Enumerator,
            Base,
            Depth + 1,
            1
        )
    ).

-file("src/spruce/list.gleam", 99).
-spec render_items(
    spruce:spruce(),
    items(),
    kind(),
    enumerator(),
    binary(),
    integer(),
    integer()
) -> list(binary()).
render_items(Sp, Items, Kind, Enumerator, Base, Depth, Index) ->
    case Items of
        empty ->
            [];

        {cons, First, Rest} ->
            _pipe = render_item(Sp, First, Kind, Enumerator, Base, Depth, Index),
            lists:append(
                _pipe,
                render_items(Sp, Rest, Kind, Enumerator, Base, Depth, Index + 1)
            )
    end.

-file("src/spruce/list.gleam", 92).
?DOC(" Render a list to a string.\n").
-spec render(spruce:spruce(), list_()) -> binary().
render(Sp, List_) ->
    Base = gleam@string:repeat(<<"  "/utf8>>, spruce:depth(Sp)),
    _pipe = render_items(
        Sp,
        erlang:element(2, List_),
        erlang:element(3, List_),
        erlang:element(4, List_),
        Base,
        1,
        1
    ),
    gleam@string:join(_pipe, <<"\n"/utf8>>).