-module(spruce@palette).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/spruce/palette.gleam").
-export([hash/2]).
-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(
" Deterministic hash-based colors for consistent terminal output.\n"
"\n"
" The `palette` module maps strings to colors using a simple hash function,\n"
" ensuring that the same input always produces the same color. This is useful\n"
" for coloring log categories, service names, user IDs, or any other repeated\n"
" identifiers in a visually consistent way.\n"
"\n"
" The palette automatically adapts to the terminal's color support:\n"
" - When color is disabled (`NoColor`), `hash` returns a plain style\n"
" - When 256-color or truecolor is available, it uses a broader palette\n"
" - When only basic ANSI is available, it falls back to a smaller set\n"
"\n"
" ```gleam\n"
" import spruce\n"
" import spruce/palette\n"
" import spruce/style\n"
"\n"
" pub fn main() {\n"
" let sp = spruce.detect()\n"
" let colored = style.render(sp, palette.hash(sp, \"database\"), \"database\")\n"
" // \"database\" will always be rendered with the same color\n"
" }\n"
" ```\n"
).
-file("src/spruce/palette.gleam", 63).
-spec bounded_modulo(integer(), integer()) -> integer().
bounded_modulo(Value, Modulus) ->
case gleam@int:modulo(Value, Modulus) of
{ok, Value@1} ->
Value@1;
{error, nil} ->
0
end.
-file("src/spruce/palette.gleam", 54).
-spec hash_text(binary()) -> integer().
hash_text(Text) ->
_pipe = Text,
_pipe@1 = gleam@string:to_utf_codepoints(_pipe),
gleam@list:fold(
_pipe@1,
5381,
fun(Hash, Cp) ->
Next = (Hash * 33) + gleam_stdlib:identity(Cp),
bounded_modulo(Next, 2147483647)
end
).
-file("src/spruce/palette.gleam", 70).
-spec palette_for(tty:color_level()) -> list(spruce@style:color()).
palette_for(Level) ->
case tty:color_level_at_least(Level, ansi256) of
true ->
[red,
green,
yellow,
blue,
magenta,
cyan,
bright_red,
bright_green,
bright_yellow,
bright_blue,
bright_magenta,
bright_cyan];
false ->
[cyan, green, yellow, magenta, blue, red]
end.
-file("src/spruce/palette.gleam", 39).
?DOC(
" Map a string to a deterministic color style.\n"
"\n"
" The same input string will always produce the same color. The color palette\n"
" adapts to the context's color level: a broader set of colors is used when\n"
" 256-color or truecolor support is detected, and a smaller set for basic ANSI.\n"
"\n"
" When the context has `NoColor`, this returns a plain style with no color.\n"
).
-spec hash(spruce:spruce(), binary()) -> spruce@style:style().
hash(Sp, Text) ->
case spruce:supports_color(Sp) of
false ->
spruce@style:new();
true ->
Colors = palette_for(spruce:color_level(Sp)),
Index = case erlang:length(Colors) of
0 -> 0;
Gleam@denominator -> hash_text(Text) rem Gleam@denominator
end,
Color = case gleam@list:drop(Colors, Index) of
[C | _] ->
C;
[] ->
cyan
end,
_pipe = spruce@style:new(),
spruce@style:fg(_pipe, Color)
end.