-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.