clan-cli: clan_cli.colors -> clan_lib.colors

This commit is contained in:
lassulus
2025-05-20 12:52:12 +02:00
parent d15d135e23
commit 33f3f01e56
5 changed files with 4 additions and 4 deletions

View File

@@ -19,9 +19,9 @@ from pathlib import Path
from typing import IO, Any
from clan_cli.async_run import get_async_ctx, is_async_cancelled
from clan_cli.colors import Color
from clan_cli.custom_logger import print_trace
from clan_lib.colors import Color
from clan_lib.errors import ClanCmdError, ClanError, CmdOut, indent_command
cmdlog = logging.getLogger(__name__)

View File

@@ -0,0 +1,159 @@
from enum import Enum
from typing import Any
ANSI16_MARKER = 300
ANSI256_MARKER = 301
DEFAULT_MARKER = 302
class RgbColor(Enum):
"""
A subset of CSS colors with RGB values that work well in Dark and Light mode.
"""
TEAL = (0, 130, 128)
OLIVEDRAB = (113, 122, 57)
CHOCOLATE = (198, 77, 45)
MEDIUMORCHID = (170, 74, 198)
SEAGREEN = (49, 128, 125)
SLATEBLUE = (127, 97, 206)
DARKCYAN = (25, 128, 145)
STEELBLUE = (51, 118, 193)
MEDIUMPURPLE = (149, 82, 220)
INDIANRED = (199, 74, 77)
FORESTGREEN = (42, 134, 44)
SLATEGRAY = (75, 123, 140)
LIGHTSLATEGRAY = (125, 106, 170)
MEDIUMSLATEBLUE = (100, 92, 255)
GRAY = (144, 102, 146)
DARKORCHID = (188, 36, 228)
SIENNA = (178, 94, 30)
OLIVE = (133, 116, 33)
PALEVIOLETRED = (186, 77, 136)
DARKGOLDENROD = (180, 93, 0)
MEDIUMVIOLETRED = (212, 2, 184)
BLUEVIOLET = (165, 50, 255)
DIMGRAY = (95, 122, 115)
DARKVIOLET = (202, 12, 211)
DODGERBLUE = (0, 106, 255)
DARKOLIVEGREEN = (88, 128, 41)
@classmethod
def get_by_name(cls: Any, name: str) -> "RgbColor":
try:
return cls[name.upper()]
except KeyError as ex:
msg = f"Color '{name}' is not a valid color name"
raise ValueError(msg) from ex
@classmethod
def list_values(cls: Any) -> list[tuple[int, int, int]]:
return [color.value for color in cls]
class AnsiColor(Enum):
"""Enum representing ANSI colors."""
# Standard 16-bit colors
BLACK = (ANSI16_MARKER, 0, 0)
RED = (ANSI16_MARKER, 1, 0)
GREEN = (ANSI16_MARKER, 2, 0)
YELLOW = (ANSI16_MARKER, 3, 0)
BLUE = (ANSI16_MARKER, 4, 0)
MAGENTA = (ANSI16_MARKER, 5, 0)
CYAN = (ANSI16_MARKER, 6, 0)
WHITE = (ANSI16_MARKER, 7, 0)
DEFAULT = (DEFAULT_MARKER, 9, 0)
# Subset of 256-bit colors
BRIGHT_BLACK = (ANSI256_MARKER, 8, 0)
BRIGHT_RED = (ANSI256_MARKER, 9, 0)
BRIGHT_GREEN = (ANSI256_MARKER, 10, 0)
BRIGHT_YELLOW = (ANSI256_MARKER, 11, 0)
BRIGHT_BLUE = (ANSI256_MARKER, 12, 0)
BRIGHT_MAGENTA = (ANSI256_MARKER, 13, 0)
BRIGHT_CYAN = (ANSI256_MARKER, 14, 0)
BRIGHT_WHITE = (ANSI256_MARKER, 15, 0)
Color = AnsiColor | RgbColor
class ColorType(Enum):
BG = 40
FG = 30
def _join(*values: int | str) -> str:
"""
Join a series of values with semicolons. The values
are either integers or strings, so stringify each for
good measure. Worth breaking out as its own function
because semicolon-joined lists are core to ANSI coding.
"""
return ";".join(str(v) for v in values)
def color_code(spec: tuple[int, int, int], base: ColorType) -> str:
"""
Workhorse of encoding a color. Give preference to named colors from
ANSI, then to specific numeric or tuple specs. If those don't work,
try looking up CSS color names or parsing CSS color specifications
(hex or rgb).
"""
red = spec[0]
green = spec[1]
blue = spec[2]
val = None
if red == ANSI16_MARKER:
val = _join(base.value + green)
elif red == ANSI256_MARKER:
val = _join(base.value + 8, 5, green)
elif red == DEFAULT_MARKER:
val = _join(base.value + 9)
elif 0 <= red <= 255 and 0 <= green <= 255 and 0 <= blue <= 255:
val = _join(base.value + 8, 2, red, green, blue)
else:
msg = f"Invalid color specification: {spec}"
raise ValueError(msg)
return val
def color_by_tuple(
message: str,
fg: tuple[int, int, int] = AnsiColor.DEFAULT.value,
bg: tuple[int, int, int] = AnsiColor.DEFAULT.value,
) -> str:
codes: list[str] = []
if fg[0] != DEFAULT_MARKER:
codes.append(color_code(fg, ColorType.FG))
if bg[0] != DEFAULT_MARKER:
codes.append(color_code(bg, ColorType.BG))
if codes:
template = "\x1b[{0}m{1}\x1b[0m"
return template.format(_join(*codes), message)
return message
def color(
message: str,
fg: Color = AnsiColor.DEFAULT,
bg: Color = AnsiColor.DEFAULT,
) -> str:
"""
Add ANSI colors and styles to a string.
"""
return color_by_tuple(message, fg.value, bg.value)
if __name__ == "__main__":
print("====ANSI Colors====")
for _, value in AnsiColor.__members__.items():
print(color_by_tuple(f"{value}", fg=value.value))
print("====CSS Colors====")
for _, cs_value in RgbColor.__members__.items():
print(color_by_tuple(f"{cs_value}", fg=cs_value.value))