-module(spruce@tree).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/spruce/tree.gleam").
-export([root/1, child/2, ascii/1, enumerator/2, render/2]).
-export_type([branches/0, tree/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 nested tree rendering.\n"
"\n"
" Trees are data structures rendered with an explicit `Spruce` context. The\n"
" default renderer uses Unicode branch glyphs when color is supported and a\n"
" deterministic ASCII fallback when it is not.\n"
).
-type branches() :: auto |
ascii |
{custom, fun((integer(), boolean()) -> binary())}.
-opaque tree() :: {tree, binary(), list(tree()), branches()}.
-file("src/spruce/tree.gleam", 25).
?DOC(" Build a tree root with no children.\n").
-spec root(binary()) -> tree().
root(Label) ->
{tree, Label, [], auto}.
-file("src/spruce/tree.gleam", 30).
?DOC(" Add a child to `parent`, preserving insertion order.\n").
-spec child(tree(), tree()) -> tree().
child(Parent, Child) ->
{tree,
erlang:element(2, Parent),
lists:append(erlang:element(3, Parent), [Child]),
erlang:element(4, Parent)}.
-file("src/spruce/tree.gleam", 35).
?DOC(" Force deterministic ASCII branch markers regardless of color support.\n").
-spec ascii(tree()) -> tree().
ascii(Tree) ->
{tree, erlang:element(2, Tree), erlang:element(3, Tree), ascii}.
-file("src/spruce/tree.gleam", 44).
?DOC(
" Set a custom branch enumerator for the rendered tree.\n"
"\n"
" The function receives the one-based depth of the node being rendered and\n"
" whether it is the last child of its parent. It should return the complete\n"
" branch marker to place before that node's first label line.\n"
).
-spec enumerator(tree(), fun((integer(), boolean()) -> binary())) -> tree().
enumerator(Tree, Branch) ->
{tree, erlang:element(2, Tree), erlang:element(3, Tree), {custom, Branch}}.
-file("src/spruce/tree.gleam", 107).
-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/tree.gleam", 196).
-spec spaces_like(binary()) -> binary().
spaces_like(Text) ->
gleam@string:repeat(<<" "/utf8>>, spruce@align:visual_length(Text)).
-file("src/spruce/tree.gleam", 191).
-spec ascii_ancestor(boolean()) -> binary().
ascii_ancestor(Last) ->
gleam@bool:guard(Last, <<" "/utf8>>, fun() -> <<"| "/utf8>> end).
-file("src/spruce/tree.gleam", 186).
-spec unicode_ancestor(boolean()) -> binary().
unicode_ancestor(Last) ->
gleam@bool:guard(Last, <<" "/utf8>>, fun() -> <<"│ "/utf8>> end).
-file("src/spruce/tree.gleam", 158).
-spec ancestor_token(spruce:spruce(), branches(), integer(), boolean()) -> binary().
ancestor_token(Sp, Branches, Depth, Last) ->
case Branches of
auto ->
case spruce:supports_color(Sp) of
true ->
unicode_ancestor(Last);
false ->
ascii_ancestor(Last)
end;
ascii ->
ascii_ancestor(Last);
{custom, Branch} ->
spaces_like(Branch(Depth, Last))
end.
-file("src/spruce/tree.gleam", 149).
-spec follow_token(spruce:spruce(), branches(), integer(), boolean()) -> binary().
follow_token(Sp, Branches, Depth, Last) ->
ancestor_token(Sp, Branches, Depth, Last).
-file("src/spruce/tree.gleam", 181).
-spec ascii_branch(boolean()) -> binary().
ascii_branch(Last) ->
gleam@bool:guard(Last, <<"`- "/utf8>>, fun() -> <<"|- "/utf8>> end).
-file("src/spruce/tree.gleam", 176).
-spec unicode_branch(boolean()) -> binary().
unicode_branch(Last) ->
gleam@bool:guard(Last, <<"└─ "/utf8>>, fun() -> <<"├─ "/utf8>> end).
-file("src/spruce/tree.gleam", 131).
-spec branch_token(spruce:spruce(), branches(), integer(), boolean()) -> binary().
branch_token(Sp, Branches, Depth, Last) ->
case Branches of
auto ->
case spruce:supports_color(Sp) of
true ->
unicode_branch(Last);
false ->
ascii_branch(Last)
end;
ascii ->
ascii_branch(Last);
{custom, Branch} ->
Branch(Depth, Last)
end.
-file("src/spruce/tree.gleam", 117).
-spec ancestor_prefix(spruce:spruce(), branches(), list(boolean()), integer()) -> binary().
ancestor_prefix(Sp, Branches, Ancestors, Depth) ->
case Ancestors of
[] ->
<<""/utf8>>;
[Last | Rest] ->
<<(ancestor_token(Sp, Branches, Depth, Last))/binary,
(ancestor_prefix(Sp, Branches, Rest, Depth + 1))/binary>>
end.
-file("src/spruce/tree.gleam", 83).
-spec render_node(
spruce:spruce(),
tree(),
branches(),
integer(),
list(boolean()),
binary(),
boolean()
) -> list(binary()).
render_node(Sp, Tree, Branches, Depth, Ancestors, Base, Last) ->
Ancestor = ancestor_prefix(Sp, Branches, Ancestors, 1),
Prefix = <<<<Base/binary, Ancestor/binary>>/binary,
(branch_token(Sp, Branches, Depth, Last))/binary>>,
Follow = <<<<Base/binary, Ancestor/binary>>/binary,
(follow_token(Sp, Branches, Depth, Last))/binary>>,
_pipe = render_label(Prefix, Follow, erlang:element(2, Tree)),
lists:append(
_pipe,
render_children(
Sp,
erlang:element(3, Tree),
Branches,
Depth + 1,
lists:append(Ancestors, [Last]),
Base
)
).
-file("src/spruce/tree.gleam", 65).
-spec render_children(
spruce:spruce(),
list(tree()),
branches(),
integer(),
list(boolean()),
binary()
) -> list(binary()).
render_children(Sp, Children, Branches, Depth, Ancestors, Base) ->
case Children of
[] ->
[];
[Last_child] ->
render_node(Sp, Last_child, Branches, Depth, Ancestors, Base, true);
[First | Rest] ->
_pipe = render_node(
Sp,
First,
Branches,
Depth,
Ancestors,
Base,
false
),
lists:append(
_pipe,
render_children(Sp, Rest, Branches, Depth, Ancestors, Base)
)
end.
-file("src/spruce/tree.gleam", 49).
?DOC(" Render a tree to a string.\n").
-spec render(spruce:spruce(), tree()) -> binary().
render(Sp, Tree) ->
Base = gleam@string:repeat(<<" "/utf8>>, spruce:depth(Sp)),
Lines = begin
_pipe = render_label(Base, Base, erlang:element(2, Tree)),
lists:append(
_pipe,
render_children(
Sp,
erlang:element(3, Tree),
erlang:element(4, Tree),
1,
[],
Base
)
)
end,
gleam@string:join(Lines, <<"\n"/utf8>>).