Skip to main content

src/spruce@output.erl

-module(spruce@output).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/spruce/output.gleam").
-export([new/1, context/1, append/2, text/2, blank/1, group/3, to_string/1, print/1]).
-export_type([output/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(
    " Pipeable output accumulation.\n"
    "\n"
    " An `Output` threads a `Spruce` context and a buffer of rendered blocks\n"
    " through a pipeline, so several renderers compose with `|>` and emit\n"
    " together. It stays pure: nothing is printed until `print`, and the context\n"
    " is threaded for you so each renderer sees the right color level and indent\n"
    " depth.\n"
    "\n"
    " ```gleam\n"
    " import spruce\n"
    " import spruce/message\n"
    " import spruce/output\n"
    "\n"
    " pub fn main() {\n"
    "   let sp = spruce.detect()\n"
    "\n"
    "   output.new(sp)\n"
    "   |> output.append(message.start(_, \"compiling\"))\n"
    "   |> output.group(\"Tests\", fn(o) {\n"
    "     o |> output.append(message.info(_, \"running\"))\n"
    "   })\n"
    "   |> output.print\n"
    " }\n"
    " ```\n"
    "\n"
    " `append` accepts any `Spruce -> String` renderer via a `_` capture, so it\n"
    " works with every spruce module without per-type variants. For eager,\n"
    " streaming grouping that prints as work happens and can return a value, use\n"
    " `spruce/group.group` instead.\n"
).

-opaque output() :: {output, spruce:spruce(), list(binary())}.

-file("src/spruce/output.gleam", 45).
?DOC(" Start an empty output that renders with `sp`.\n").
-spec new(spruce:spruce()) -> output().
new(Sp) ->
    {output, Sp, []}.

-file("src/spruce/output.gleam", 51).
?DOC(
    " The context the output renders with. Reflects the current group depth inside\n"
    " a `group` body.\n"
).
-spec context(output()) -> spruce:spruce().
context(Output) ->
    erlang:element(2, Output).

-file("src/spruce/output.gleam", 58).
?DOC(
    " Append a rendered block produced by `render`, which receives the output's\n"
    " context. Works with any `Spruce -> String` renderer via a `_` capture, e.g.\n"
    " `output.append(message.success(_, \"done\"))`.\n"
).
-spec append(output(), fun((spruce:spruce()) -> binary())) -> output().
append(Output, Render) ->
    {output,
        erlang:element(2, Output),
        [Render(erlang:element(2, Output)) | erlang:element(3, Output)]}.

-file("src/spruce/output.gleam", 63).
?DOC(" Append a raw string as-is, without rendering.\n").
-spec text(output(), binary()) -> output().
text(Output, Text) ->
    {output, erlang:element(2, Output), [Text | erlang:element(3, Output)]}.

-file("src/spruce/output.gleam", 68).
?DOC(" Append a blank line.\n").
-spec blank(output()) -> output().
blank(Output) ->
    {output,
        erlang:element(2, Output),
        [<<""/utf8>> | erlang:element(3, Output)]}.

-file("src/spruce/output.gleam", 75).
?DOC(
    " Append a styled group title, then run `body` with the output's context\n"
    " indented one level deeper. Blocks appended inside `body` nest under the\n"
    " title. Unlike `spruce/group.group`, this buffers output rather than printing.\n"
).
-spec group(output(), binary(), fun((output()) -> output())) -> output().
group(Output, Title, Body) ->
    Titled = text(
        Output,
        spruce@group:render_title(erlang:element(2, Output), Title)
    ),
    Body_output = Body(
        {output,
            spruce:indented(erlang:element(2, Output)),
            erlang:element(3, Titled)}
    ),
    {output, erlang:element(2, Output), erlang:element(3, Body_output)}.

-file("src/spruce/output.gleam", 87).
?DOC(" Render the accumulated output to a single string, blocks joined by newlines.\n").
-spec to_string(output()) -> binary().
to_string(Output) ->
    _pipe = erlang:element(3, Output),
    _pipe@1 = lists:reverse(_pipe),
    gleam@string:join(_pipe@1, <<"\n"/utf8>>).

-file("src/spruce/output.gleam", 94).
?DOC(" Print the accumulated output to stdout.\n").
-spec print(output()) -> nil.
print(Output) ->
    gleam_stdlib:println(to_string(Output)).