-module(spruce@box). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/spruce/box.gleam"). -export([default_options/0, options/2, padding/5, margin/5, border/2, border_colors/5, border_sides/5, width/2, render/3, simple/2, print/2]). -export_type([border_chars/0, border/0, box_options/0, box_config/0, sides/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(" Boxed terminal output with rounded borders and an optional title.\n"). -type border_chars() :: {border_chars, binary(), binary(), binary(), binary(), binary(), binary(), binary(), binary()}. -type border() :: normal | rounded | thick | double | hidden | block | {custom, border_chars()}. -opaque box_options() :: {box_options, binary(), spruce@style:color()} | {configured_box_options, binary(), spruce@style:color(), integer(), integer(), integer(), integer(), integer(), integer(), integer(), integer(), border(), gleam@option:option(integer()), spruce@style:color(), spruce@style:color(), spruce@style:color(), spruce@style:color(), boolean(), boolean(), boolean(), boolean()}. -type box_config() :: {box_config, binary(), spruce@style:color(), integer(), integer(), integer(), integer(), integer(), integer(), integer(), integer(), border(), gleam@option:option(integer()), spruce@style:color(), spruce@style:color(), spruce@style:color(), spruce@style:color(), boolean(), boolean(), boolean(), boolean()}. -type sides() :: {sides, boolean(), boolean(), boolean(), boolean()}. -file("src/spruce/box.gleam", 103). ?DOC(" Default options: no title, cyan border.\n"). -spec default_options() -> box_options(). default_options() -> {box_options, <<""/utf8>>, cyan}. -file("src/spruce/box.gleam", 108). ?DOC(" Build options with an explicit title and border color.\n"). -spec options(binary(), spruce@style:color()) -> box_options(). options(Title, Color) -> {box_options, Title, Color}. -file("src/spruce/box.gleam", 678). -spec non_negative(integer()) -> integer(). non_negative(Value) -> gleam@int:max(0, Value). -file("src/spruce/box.gleam", 374). -spec configured(box_config()) -> box_options(). configured(Config) -> {configured_box_options, erlang:element(2, Config), erlang:element(3, Config), erlang:element(4, Config), erlang:element(5, Config), erlang:element(6, Config), erlang:element(7, Config), erlang:element(8, Config), erlang:element(9, Config), erlang:element(10, Config), erlang:element(11, Config), erlang:element(12, Config), erlang:element(13, Config), erlang:element(14, Config), erlang:element(15, Config), erlang:element(16, Config), erlang:element(17, Config), erlang:element(18, Config), erlang:element(19, Config), erlang:element(20, Config), erlang:element(21, Config)}. -file("src/spruce/box.gleam", 349). -spec default_config(binary(), spruce@style:color()) -> box_config(). default_config(Title, Color) -> {box_config, Title, Color, 0, 1, 0, 1, 0, 0, 0, 0, rounded, none, Color, Color, Color, Color, true, true, true, true}. -file("src/spruce/box.gleam", 299). -spec box_config(box_options()) -> box_config(). box_config(Options) -> case Options of {box_options, Title, Color} -> default_config(Title, Color); {configured_box_options, Title@1, Color@1, Padding_top, Padding_right, Padding_bottom, Padding_left, Margin_top, Margin_right, Margin_bottom, Margin_left, Border, Width, Border_top_color, Border_right_color, Border_bottom_color, Border_left_color, Border_top, Border_right, Border_bottom, Border_left} -> {box_config, Title@1, Color@1, Padding_top, Padding_right, Padding_bottom, Padding_left, Margin_top, Margin_right, Margin_bottom, Margin_left, Border, Width, Border_top_color, Border_right_color, Border_bottom_color, Border_left_color, Border_top, Border_right, Border_bottom, Border_left} end. -file("src/spruce/box.gleam", 113). ?DOC(" Set inner padding as top, right, bottom, left cell counts.\n"). -spec padding(box_options(), integer(), integer(), integer(), integer()) -> box_options(). padding(Options, Top, Right, Bottom, Left) -> Config = box_config(Options), configured( {box_config, erlang:element(2, Config), erlang:element(3, Config), non_negative(Top), non_negative(Right), non_negative(Bottom), non_negative(Left), erlang:element(8, Config), erlang:element(9, Config), erlang:element(10, Config), erlang:element(11, Config), erlang:element(12, Config), erlang:element(13, Config), erlang:element(14, Config), erlang:element(15, Config), erlang:element(16, Config), erlang:element(17, Config), erlang:element(18, Config), erlang:element(19, Config), erlang:element(20, Config), erlang:element(21, Config)} ). -file("src/spruce/box.gleam", 134). ?DOC(" Set outer margin as top, right, bottom, left cell counts.\n"). -spec margin(box_options(), integer(), integer(), integer(), integer()) -> box_options(). margin(Options, Top, Right, Bottom, Left) -> Config = box_config(Options), configured( {box_config, erlang:element(2, Config), erlang:element(3, Config), erlang:element(4, Config), erlang:element(5, Config), erlang:element(6, Config), erlang:element(7, Config), non_negative(Top), non_negative(Right), non_negative(Bottom), non_negative(Left), erlang:element(12, Config), erlang:element(13, Config), erlang:element(14, Config), erlang:element(15, Config), erlang:element(16, Config), erlang:element(17, Config), erlang:element(18, Config), erlang:element(19, Config), erlang:element(20, Config), erlang:element(21, Config)} ). -file("src/spruce/box.gleam", 155). ?DOC(" Set the border style.\n"). -spec border(box_options(), border()) -> box_options(). border(Options, Border) -> Config = box_config(Options), configured( {box_config, erlang:element(2, Config), erlang:element(3, Config), erlang:element(4, Config), erlang:element(5, Config), erlang:element(6, Config), erlang:element(7, Config), erlang:element(8, Config), erlang:element(9, Config), erlang:element(10, Config), erlang:element(11, Config), Border, erlang:element(13, Config), erlang:element(14, Config), erlang:element(15, Config), erlang:element(16, Config), erlang:element(17, Config), erlang:element(18, Config), erlang:element(19, Config), erlang:element(20, Config), erlang:element(21, Config)} ). -file("src/spruce/box.gleam", 161). ?DOC(" Set top, right, bottom, and left border colors independently.\n"). -spec border_colors( box_options(), spruce@style:color(), spruce@style:color(), spruce@style:color(), spruce@style:color() ) -> box_options(). border_colors(Options, Top, Right, Bottom, Left) -> Config = box_config(Options), configured( {box_config, erlang:element(2, Config), erlang:element(3, Config), erlang:element(4, Config), erlang:element(5, Config), erlang:element(6, Config), erlang:element(7, Config), erlang:element(8, Config), erlang:element(9, Config), erlang:element(10, Config), erlang:element(11, Config), erlang:element(12, Config), erlang:element(13, Config), Top, Right, Bottom, Left, erlang:element(18, Config), erlang:element(19, Config), erlang:element(20, Config), erlang:element(21, Config)} ). -file("src/spruce/box.gleam", 182). ?DOC(" Set top, right, bottom, and left border visibility independently.\n"). -spec border_sides(box_options(), boolean(), boolean(), boolean(), boolean()) -> box_options(). border_sides(Options, Top, Right, Bottom, Left) -> Config = box_config(Options), configured( {box_config, erlang:element(2, Config), erlang:element(3, Config), erlang:element(4, Config), erlang:element(5, Config), erlang:element(6, Config), erlang:element(7, Config), erlang:element(8, Config), erlang:element(9, Config), erlang:element(10, Config), erlang:element(11, Config), erlang:element(12, Config), erlang:element(13, Config), erlang:element(14, Config), erlang:element(15, Config), erlang:element(16, Config), erlang:element(17, Config), Top, Right, Bottom, Left} ). -file("src/spruce/box.gleam", 203). ?DOC(" Constrain content to a visual width, wrapping with `spruce/align.wrap`.\n"). -spec width(box_options(), integer()) -> box_options(). width(Options, Width) -> Config = box_config(Options), configured( {box_config, erlang:element(2, Config), erlang:element(3, Config), erlang:element(4, Config), erlang:element(5, Config), erlang:element(6, Config), erlang:element(7, Config), erlang:element(8, Config), erlang:element(9, Config), erlang:element(10, Config), erlang:element(11, Config), erlang:element(12, Config), {some, non_negative(Width)}, erlang:element(14, Config), erlang:element(15, Config), erlang:element(16, Config), erlang:element(17, Config), erlang:element(18, Config), erlang:element(19, Config), erlang:element(20, Config), erlang:element(21, Config)} ). -file("src/spruce/box.gleam", 667). -spec repeat_line_loop(binary(), integer(), list(binary())) -> list(binary()). repeat_line_loop(Line, Count, Acc) -> case Count of 0 -> Acc; _ -> repeat_line_loop(Line, Count - 1, [Line | Acc]) end. -file("src/spruce/box.gleam", 663). -spec repeat_line(binary(), integer()) -> list(binary()). repeat_line(Line, Count) -> repeat_line_loop(Line, non_negative(Count), []). -file("src/spruce/box.gleam", 657). -spec find_max_width(list(binary()), integer()) -> integer(). find_max_width(Lines, Min_width) -> _pipe = Lines, _pipe@1 = gleam@list:map(_pipe, fun spruce@align:visual_length/1), gleam@list:fold(_pipe@1, Min_width, fun gleam@int:max/2). -file("src/spruce/box.gleam", 633). -spec apply_margin( list(binary()), binary(), integer(), integer(), integer(), integer() ) -> list(binary()). apply_margin(Lines, Prefix, Top, Right, Bottom, Left) -> Line_width = find_max_width(Lines, 0), Blank = gleam@string:repeat(<<" "/utf8>>, (Left + Line_width) + Right), Top_rows = repeat_line(<>, Top), Body = gleam@list:map( Lines, fun(Line) -> <<<<<>, Left))/binary>>/binary, (spruce@align:pad_right(Line, Line_width))/binary>>/binary, (gleam@string:repeat(<<" "/utf8>>, Right))/binary>> end ), _pipe = Top_rows, _pipe@1 = lists:append(_pipe, Body), lists:append(_pipe@1, repeat_line(<>, Bottom)). -file("src/spruce/box.gleam", 547). -spec render_bottom( integer(), sides(), border_chars(), fun((binary()) -> binary()) ) -> binary(). render_bottom(Inner_width, Sides, Chars, Paint_bottom) -> Left = case erlang:element(5, Sides) of true -> erlang:element(8, Chars); false -> <<""/utf8>> end, Right = case erlang:element(3, Sides) of true -> erlang:element(6, Chars); false -> <<""/utf8>> end, Paint_bottom( <<<>/binary, Right/binary>> ). -file("src/spruce/box.gleam", 504). -spec append_bottom( list(binary()), sides(), integer(), border_chars(), fun((binary()) -> binary()) ) -> list(binary()). append_bottom(Lines, Sides, Inner_width, Chars, Paint_bottom) -> gleam@bool:guard( erlang:element(4, Sides) =:= false, Lines, fun() -> lists:append( Lines, [render_bottom(Inner_width, Sides, Chars, Paint_bottom)] ) end ). -file("src/spruce/box.gleam", 516). -spec render_top( binary(), integer(), integer(), sides(), border_chars(), fun((binary()) -> binary()) ) -> binary(). render_top(Title, Title_width, Inner_width, Sides, Chars, Paint_top) -> Left = case erlang:element(5, Sides) of true -> erlang:element(2, Chars); false -> <<""/utf8>> end, Right = case erlang:element(3, Sides) of true -> erlang:element(4, Chars); false -> <<""/utf8>> end, case Title of <<""/utf8>> -> Paint_top( <<<>/binary, Right/binary>> ); Title@1 -> <<<<<<<<(Paint_top( <> ))/binary, " "/utf8>>/binary, Title@1/binary>>/binary, " "/utf8>>/binary, (Paint_top( <<(gleam@string:repeat( erlang:element(3, Chars), gleam@int:max(0, (Inner_width - Title_width) - 3) ))/binary, Right/binary>> ))/binary>> end. -file("src/spruce/box.gleam", 623). -spec side_right(sides(), border_chars(), fun((binary()) -> binary())) -> binary(). side_right(Sides, Chars, Paint_right) -> gleam@bool:guard( erlang:element(3, Sides) =:= false, <<""/utf8>>, fun() -> Paint_right(erlang:element(5, Chars)) end ). -file("src/spruce/box.gleam", 613). -spec side_left(sides(), border_chars(), fun((binary()) -> binary())) -> binary(). side_left(Sides, Chars, Paint_left) -> gleam@bool:guard( erlang:element(5, Sides) =:= false, <<""/utf8>>, fun() -> Paint_left(erlang:element(9, Chars)) end ). -file("src/spruce/box.gleam", 599). -spec render_blank_row( integer(), sides(), border_chars(), fun((binary()) -> binary()), fun((binary()) -> binary()) ) -> binary(). render_blank_row(Inner_width, Sides, Chars, Paint_left, Paint_right) -> Inner = gleam@string:repeat(<<" "/utf8>>, Inner_width), <<<<(side_left(Sides, Chars, Paint_left))/binary, Inner/binary>>/binary, (side_right(Sides, Chars, Paint_right))/binary>>. -file("src/spruce/box.gleam", 585). -spec blank_rows( integer(), integer(), sides(), border_chars(), fun((binary()) -> binary()), fun((binary()) -> binary()) ) -> list(binary()). blank_rows(Count, Inner_width, Sides, Chars, Paint_left, Paint_right) -> repeat_line( render_blank_row(Inner_width, Sides, Chars, Paint_left, Paint_right), Count ). -file("src/spruce/box.gleam", 565). -spec render_body_line( binary(), integer(), integer(), integer(), sides(), border_chars(), fun((binary()) -> binary()), fun((binary()) -> binary()) ) -> binary(). render_body_line( Line, Text_width, Padding_left, Padding_right, Sides, Chars, Paint_left, Paint_right ) -> Inner = <<<<(gleam@string:repeat(<<" "/utf8>>, Padding_left))/binary, (spruce@align:pad_right(Line, Text_width))/binary>>/binary, (gleam@string:repeat(<<" "/utf8>>, Padding_right))/binary>>, <<<<(side_left(Sides, Chars, Paint_left))/binary, Inner/binary>>/binary, (side_right(Sides, Chars, Paint_right))/binary>>. -file("src/spruce/box.gleam", 498). -spec border_painter(spruce:spruce(), spruce@style:color()) -> fun((binary()) -> binary()). border_painter(Sp, Color) -> Border_style = begin _pipe = spruce@style:new(), spruce@style:fg(_pipe, Color) end, fun(Text) -> spruce@style:render(Sp, Border_style, Text) end. -file("src/spruce/box.gleam", 412). -spec border_chars(border()) -> border_chars(). border_chars(Border) -> case Border of normal -> {border_chars, <<"┌"/utf8>>, <<"─"/utf8>>, <<"┐"/utf8>>, <<"│"/utf8>>, <<"┘"/utf8>>, <<"─"/utf8>>, <<"└"/utf8>>, <<"│"/utf8>>}; rounded -> {border_chars, <<"╭"/utf8>>, <<"─"/utf8>>, <<"╮"/utf8>>, <<"│"/utf8>>, <<"╯"/utf8>>, <<"─"/utf8>>, <<"╰"/utf8>>, <<"│"/utf8>>}; thick -> {border_chars, <<"┏"/utf8>>, <<"━"/utf8>>, <<"┓"/utf8>>, <<"┃"/utf8>>, <<"┛"/utf8>>, <<"━"/utf8>>, <<"┗"/utf8>>, <<"┃"/utf8>>}; double -> {border_chars, <<"╔"/utf8>>, <<"═"/utf8>>, <<"╗"/utf8>>, <<"║"/utf8>>, <<"╝"/utf8>>, <<"═"/utf8>>, <<"╚"/utf8>>, <<"║"/utf8>>}; block -> {border_chars, <<"█"/utf8>>, <<"█"/utf8>>, <<"█"/utf8>>, <<"█"/utf8>>, <<"█"/utf8>>, <<"█"/utf8>>, <<"█"/utf8>>, <<"█"/utf8>>}; hidden -> {border_chars, <<""/utf8>>, <<""/utf8>>, <<""/utf8>>, <<""/utf8>>, <<""/utf8>>, <<""/utf8>>, <<""/utf8>>, <<""/utf8>>}; {custom, Chars} -> Chars end. -file("src/spruce/box.gleam", 491). -spec sanitize_title(binary()) -> binary(). sanitize_title(Title) -> _pipe = Title, _pipe@1 = gleam@string:replace(_pipe, <<"\r\n"/utf8>>, <<" "/utf8>>), _pipe@2 = gleam@string:replace(_pipe@1, <<"\n"/utf8>>, <<" "/utf8>>), gleam@string:replace(_pipe@2, <<"\r"/utf8>>, <<" "/utf8>>). -file("src/spruce/box.gleam", 484). -spec wrap_content(binary(), gleam@option:option(integer())) -> binary(). wrap_content(Content, Width) -> case Width of {some, Width@1} when Width@1 > 0 -> spruce@align:wrap(Content, Width@1); _ -> Content end. -file("src/spruce/box.gleam", 399). -spec effective_sides(box_config()) -> sides(). effective_sides(Config) -> case erlang:element(12, Config) of hidden -> {sides, false, false, false, false}; _ -> {sides, erlang:element(18, Config), erlang:element(19, Config), erlang:element(20, Config), erlang:element(21, Config)} end. -file("src/spruce/box.gleam", 214). ?DOC(" Render `content` in a box with the given options.\n"). -spec render(spruce:spruce(), binary(), box_options()) -> binary(). render(Sp, Content, Options) -> Config = box_config(Options), Prefix = spruce@internal@layout:indent_prefix(Sp), Sides = effective_sides(Config), Lines = begin _pipe = Content, _pipe@1 = wrap_content(_pipe, erlang:element(13, Config)), gleam@string:split(_pipe@1, <<"\n"/utf8>>) end, Title = sanitize_title(erlang:element(2, Config)), Title_width = spruce@align:visual_length(Title), Title_min_width = case Title of <<""/utf8>> -> 0; _ -> Title_width + 3 end, Content_width = find_max_width(Lines, 0), Padded_content_width = (Content_width + erlang:element(7, Config)) + erlang:element( 5, Config ), Inner_width = gleam@int:max(Padded_content_width, Title_min_width), Text_width = (Inner_width - erlang:element(7, Config)) - erlang:element( 5, Config ), Chars = border_chars(erlang:element(12, Config)), Paint_top = border_painter(Sp, erlang:element(14, Config)), Paint_right = border_painter(Sp, erlang:element(15, Config)), Paint_bottom = border_painter(Sp, erlang:element(16, Config)), Paint_left = border_painter(Sp, erlang:element(17, Config)), Body = lists:append( blank_rows( erlang:element(4, Config), Inner_width, Sides, Chars, Paint_left, Paint_right ), begin Content_rows = gleam@list:map( Lines, fun(Line) -> render_body_line( Line, Text_width, erlang:element(7, Config), erlang:element(5, Config), Sides, Chars, Paint_left, Paint_right ) end ), lists:append( Content_rows, blank_rows( erlang:element(6, Config), Inner_width, Sides, Chars, Paint_left, Paint_right ) ) end ), Boxed = begin _pipe@2 = case erlang:element(2, Sides) of true -> [render_top( Title, Title_width, Inner_width, Sides, Chars, Paint_top ) | Body]; false -> Body end, append_bottom(_pipe@2, Sides, Inner_width, Chars, Paint_bottom) end, _pipe@3 = Boxed, _pipe@4 = apply_margin( _pipe@3, Prefix, erlang:element(8, Config), erlang:element(9, Config), erlang:element(10, Config), erlang:element(11, Config) ), gleam@string:join(_pipe@4, <<"\n"/utf8>>). -file("src/spruce/box.gleam", 209). ?DOC(" Render `content` in a box using the default options.\n"). -spec simple(spruce:spruce(), binary()) -> binary(). simple(Sp, Content) -> render(Sp, Content, default_options()). -file("src/spruce/box.gleam", 683). ?DOC(" Render a default box and print it to stdout.\n"). -spec print(spruce:spruce(), binary()) -> nil. print(Sp, Content) -> gleam_stdlib:println(simple(Sp, Content)).