Skip to main content

src/spruce.erl

-module(spruce).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/spruce.gleam").
-export([detect/0, detect_stream/1, with_color_level/1, no_color/0, color_level/1, supports_color/1, background/1, with_background/2, depth/1, indented/1]).
-export_type([spruce/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(
    " spruce — a terminal-UI kit for Gleam.\n"
    "\n"
    " spruce renders styled terminal output — colors, boxes, semantic message\n"
    " lines, icons, deterministic hash-colors, ANSI-aware alignment, and\n"
    " grouped/indented output — that automatically respects the terminal's color\n"
    " support. It is logging-agnostic and targets both Erlang and JavaScript.\n"
    "\n"
    " ## The `Spruce` context\n"
    "\n"
    " Every render function takes an explicit `Spruce` value. The context carries\n"
    " two things: the detected color level (so output is plain when color is\n"
    " unsupported) and the current indent depth (so grouped output nests without\n"
    " any global state).\n"
    "\n"
    " ```gleam\n"
    " import spruce\n"
    "\n"
    " pub fn main() {\n"
    "   let sp = spruce.detect()\n"
    "   // pass `sp` to render functions in spruce/style, spruce/box, etc.\n"
    "   echo spruce.supports_color(sp)\n"
    " }\n"
    " ```\n"
    "\n"
    " Use `spruce.no_color()` in tests to get deterministic, escape-free output.\n"
).

-opaque spruce() :: {spruce, tty:color_level(), tty:background(), integer()}.

-file("src/spruce.gleam", 57).
?DOC(
    " Build a context by auto-detecting the color support of standard output.\n"
    "\n"
    " Detection honors `NO_COLOR`, `FORCE_COLOR`, `TERM`, CI environment hints,\n"
    " and TTY status (via the `tty` package), falling back to `NoColor` when\n"
    " uncertain.\n"
).
-spec detect() -> spruce().
detect() ->
    {spruce, tty:detect_color_level(stdout), tty:detect_background(stdout), 0}.

-file("src/spruce.gleam", 66).
?DOC(" Build a context by auto-detecting the color support of a specific stream.\n").
-spec detect_stream(tty:stream()) -> spruce().
detect_stream(Stream) ->
    {spruce, tty:detect_color_level(Stream), tty:detect_background(Stream), 0}.

-file("src/spruce.gleam", 78).
?DOC(
    " Build a context with an explicit color level, bypassing detection.\n"
    " Useful for forcing a level in tests or honoring a user `--color` flag.\n"
    " The background defaults to `Unknown` (treated as `Dark` by adaptive colors);\n"
    " override it with `with_background`.\n"
).
-spec with_color_level(tty:color_level()) -> spruce().
with_color_level(Level) ->
    {spruce, Level, unknown, 0}.

-file("src/spruce.gleam", 84).
?DOC(
    " Build a context that never emits color. All output is plain text.\n"
    " This is the recommended context for deterministic tests.\n"
).
-spec no_color() -> spruce().
no_color() ->
    {spruce, no_color, unknown, 0}.

-file("src/spruce.gleam", 89).
?DOC(" Get the color level of a context.\n").
-spec color_level(spruce()) -> tty:color_level().
color_level(Sp) ->
    erlang:element(2, Sp).

-file("src/spruce.gleam", 94).
?DOC(" Whether the context will emit any color (i.e. its level is not `NoColor`).\n").
-spec supports_color(spruce()) -> boolean().
supports_color(Sp) ->
    erlang:element(2, Sp) /= no_color.

-file("src/spruce.gleam", 99).
?DOC(" Get the detected terminal background of a context.\n").
-spec background(spruce()) -> tty:background().
background(Sp) ->
    erlang:element(3, Sp).

-file("src/spruce.gleam", 106).
?DOC(
    " Return a copy of the context with an explicit terminal background, bypassing\n"
    " detection. Useful for forcing light/dark adaptive colors in tests or honoring\n"
    " a user `--background` flag.\n"
).
-spec with_background(spruce(), tty:background()) -> spruce().
with_background(Sp, Background) ->
    {spruce, erlang:element(2, Sp), Background, erlang:element(4, Sp)}.

-file("src/spruce.gleam", 111).
?DOC(" Get the current indent depth of a context (0 at the top level).\n").
-spec depth(spruce()) -> integer().
depth(Sp) ->
    erlang:element(4, Sp).

-file("src/spruce.gleam", 117).
?DOC(
    " Return a copy of the context with its indent depth increased by one.\n"
    " `spruce/group` uses this to hand a deeper context to grouped bodies.\n"
).
-spec indented(spruce()) -> spruce().
indented(Sp) ->
    {spruce,
        erlang:element(2, Sp),
        erlang:element(3, Sp),
        erlang:element(4, Sp) + 1}.