-module(tty).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/tty.gleam").
-export([color_level_compare/2, color_level_at_least/2, is_tty/1, detect_color_level/1, detect_background/1]).
-export_type([stream/0, color_level/0, background/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(
" TTY and ANSI color-support detection.\n"
"\n"
" This module answers two questions a CLI program asks at startup:\n"
" 1. Is this stream connected to a terminal? (`is_tty`)\n"
" 2. What level of ANSI color does it support? (`detect_color_level`)\n"
).
-type stream() :: stdin | stdout | stderr.
-type color_level() :: no_color | basic | ansi256 | true_color.
-type background() :: light | dark | unknown.
-file("src/tty.gleam", 103).
?DOC(
" Internal capability rank for a `ColorLevel` (`NoColor`=0 .. `TrueColor`=3).\n"
" The numeric values are an implementation detail, not part of the public\n"
" 1.x API — callers should use `color_level_compare`/`color_level_at_least`.\n"
).
-spec color_level_rank(color_level()) -> integer().
color_level_rank(Level) ->
case Level of
no_color ->
0;
basic ->
1;
ansi256 ->
2;
true_color ->
3
end.
-file("src/tty.gleam", 96).
?DOC(
" Orders two color levels by capability, where\n"
" `NoColor` < `Basic` < `Ansi256` < `TrueColor`. Returns a `gleam/order`\n"
" `Order`, so it composes with `list.sort`, `order.reverse`, and friends.\n"
"\n"
" ```gleam\n"
" tty.color_level_compare(Basic, Ansi256)\n"
" // -> order.Lt\n"
" ```\n"
).
-spec color_level_compare(color_level(), color_level()) -> gleam@order:order().
color_level_compare(A, B) ->
gleam@int:compare(color_level_rank(A), color_level_rank(B)).
-file("src/tty.gleam", 78).
?DOC(
" Returns `True` if the actual color level is at least as capable as the\n"
" required level. Use this to gate features without matching every variant:\n"
"\n"
" ```gleam\n"
" let level = tty.detect_color_level(Stdout)\n"
" case tty.color_level_at_least(actual: level, at_least: Ansi256) {\n"
" True -> render_256_color()\n"
" False -> render_basic()\n"
" }\n"
" ```\n"
).
-spec color_level_at_least(color_level(), color_level()) -> boolean().
color_level_at_least(Actual, Required) ->
case color_level_compare(Actual, Required) of
lt ->
false;
eq ->
true;
gt ->
true
end.
-file("src/tty.gleam", 115).
?DOC(
" Inverse of `color_level_rank`: maps a `0..3` rank back to a `ColorLevel`,\n"
" returning `Error(Nil)` for any out-of-range value. Used to convert the\n"
" internal resolver's rank into a `ColorLevel`.\n"
).
-spec color_level_from_rank(integer()) -> {ok, color_level()} | {error, nil}.
color_level_from_rank(Rank) ->
case Rank of
0 ->
{ok, no_color};
1 ->
{ok, basic};
2 ->
{ok, ansi256};
3 ->
{ok, true_color};
_ ->
{error, nil}
end.
-file("src/tty.gleam", 128).
?DOC(
" Inverse of the internal background rank: maps a `0..2` rank back to a\n"
" `Background`, returning `Error(Nil)` for any out-of-range value. Used to\n"
" convert the internal resolver's rank into a `Background`.\n"
).
-spec background_from_rank(integer()) -> {ok, background()} | {error, nil}.
background_from_rank(Rank) ->
case Rank of
0 ->
{ok, unknown};
1 ->
{ok, dark};
2 ->
{ok, light};
_ ->
{error, nil}
end.
-file("src/tty.gleam", 151).
?DOC(
" Returns `True` if the given stream is connected to a terminal.\n"
"\n"
" On the Erlang target this uses `io:getopts/1` (requires OTP 26+). If\n"
" terminal options cannot be read, this returns `False`.\n"
" On the JavaScript target this uses `process.stdin.isTTY`,\n"
" `process.stdout.isTTY`, or `process.stderr.isTTY`, so it requires a\n"
" Node-style runtime with those streams.\n"
"\n"
" ```gleam\n"
" case tty.is_tty(Stdout) {\n"
" True -> show_spinner()\n"
" False -> print_plain_progress()\n"
" }\n"
" ```\n"
).
-spec is_tty(stream()) -> boolean().
is_tty(Stream) ->
case Stream of
stdin ->
tty_ffi:stdin_is_tty();
stdout ->
tty_ffi:stdout_is_tty();
stderr ->
tty_ffi:stderr_is_tty()
end.
-file("src/tty.gleam", 241).
-spec get_env(binary()) -> {ok, binary()} | {error, nil}.
get_env(Name) ->
tty_ffi:get_env(Name).
-file("src/tty.gleam", 177).
?DOC(
" Detects color support for a stream, honoring `NO_COLOR`, `FORCE_COLOR`,\n"
" `CI`, `TERM`, and `COLORTERM` environment variables.\n"
"\n"
" On the JavaScript target this reads `process.env`, so it requires a\n"
" Node-style runtime.\n"
"\n"
" When a JavaScript runtime does not provide `process` or `process.env`,\n"
" environment variables are treated as unset and this function falls back to\n"
" `NoColor` unless other forced inputs are available.\n"
"\n"
" ```gleam\n"
" case tty.detect_color_level(Stdout) {\n"
" NoColor -> render_without_ansi()\n"
" Basic -> render_with_basic_ansi()\n"
" Ansi256 -> render_with_256_colors()\n"
" TrueColor -> render_with_truecolor()\n"
" }\n"
" ```\n"
).
-spec detect_color_level(stream()) -> color_level().
detect_color_level(Stream) ->
Rank = tty@resolve_color_level:resolve_color_level(
is_tty(Stream),
fun get_env/1
),
Level@1 = case color_level_from_rank(Rank) of
{ok, Level} -> Level;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"resolve_color_level must return a rank in 0..3"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"tty"/utf8>>,
function => <<"detect_color_level"/utf8>>,
line => 183,
value => _assert_fail,
start => 5703,
'end' => 5753,
pattern_start => 5714,
pattern_end => 5723})
end,
Level@1.
-file("src/tty.gleam", 209).
?DOC(
" Detects whether the terminal background is light or dark from `COLORFGBG`.\n"
"\n"
" The `stream` argument is accepted for API symmetry with `detect_color_level`\n"
" and to leave room for future stream-specific detection; the current\n"
" `COLORFGBG` signal is environment-wide and stream-independent.\n"
"\n"
" On the JavaScript target this reads `process.env`, so it requires a\n"
" Node-style runtime. When a JavaScript runtime does not provide `process` or\n"
" `process.env`, environment variables are treated as unset and this function\n"
" returns `Unknown`.\n"
"\n"
" Returns `Unknown` when `COLORFGBG` is unset, malformed, or does not contain a\n"
" clear background palette index.\n"
"\n"
" ```gleam\n"
" case tty.detect_background(Stdout) {\n"
" Light -> render_dark_text()\n"
" Dark -> render_light_text()\n"
" Unknown -> render_default_theme()\n"
" }\n"
" ```\n"
).
-spec detect_background(stream()) -> background().
detect_background(_) ->
Rank = tty@resolve_background:resolve_background(fun get_env/1),
Background@1 = case background_from_rank(Rank) of
{ok, Background} -> Background;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"resolve_background must return a rank in 0..2"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"tty"/utf8>>,
function => <<"detect_background"/utf8>>,
line => 215,
value => _assert_fail,
start => 7164,
'end' => 7218,
pattern_start => 7175,
pattern_end => 7189})
end,
Background@1.