-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] -> [<> | gleam@list:map( Rest, fun(Line) -> <> 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 = <<<>/binary, (branch_token(Sp, Branches, Depth, Last))/binary>>, Follow = <<<>/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>>).