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

@@ -1,159 +0,0 @@
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))

View File

@@ -4,7 +4,7 @@ import os
import sys
from pathlib import Path
from clan_cli.colors import AnsiColor, RgbColor, color_by_tuple
from clan_lib.colors import AnsiColor, RgbColor, color_by_tuple
# https://no-color.org
DISABLE_COLOR = not sys.stderr.isatty() or os.environ.get("NO_COLOR", "") != ""

View File

@@ -9,11 +9,11 @@ from contextlib import ExitStack
from clan_lib.api import API
from clan_lib.cmd import Log, MsgColor, RunOpts, run
from clan_lib.colors import AnsiColor
from clan_lib.errors import ClanError
from clan_lib.nix import nix_command, nix_config, nix_metadata
from clan_cli.async_run import AsyncContext, AsyncOpts, AsyncRuntime, is_async_cancelled
from clan_cli.colors import AnsiColor
from clan_cli.completions import (
add_dynamic_completer,
complete_machines,

View File

@@ -14,10 +14,10 @@ from tempfile import TemporaryDirectory
from typing import Any
from clan_lib.cmd import CmdOut, RunOpts, run
from clan_lib.colors import AnsiColor
from clan_lib.errors import ClanError
from clan_lib.nix import nix_shell
from clan_cli.colors import AnsiColor
from clan_cli.ssh.host_key import HostKeyCheck
cmdlog = logging.getLogger(__name__)