2025-03-21 15:48:10 +01:00
|
|
|
from __future__ import annotations
|
2024-05-01 21:27:06 +03:00
|
|
|
import io
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
|
|
|
|
COLORIZE = True
|
|
|
|
|
2025-03-21 15:48:10 +01:00
|
|
|
# types
|
|
|
|
if False:
|
2025-05-02 20:22:31 +02:00
|
|
|
from typing import IO, Literal
|
|
|
|
|
|
|
|
type ColorTag = Literal[
|
|
|
|
"PROMPT",
|
|
|
|
"KEYWORD",
|
|
|
|
"BUILTIN",
|
|
|
|
"COMMENT",
|
|
|
|
"STRING",
|
|
|
|
"NUMBER",
|
|
|
|
"OP",
|
|
|
|
"DEFINITION",
|
|
|
|
"SOFT_KEYWORD",
|
|
|
|
"RESET",
|
|
|
|
]
|
|
|
|
|
|
|
|
theme: dict[ColorTag, str]
|
2025-03-21 15:48:10 +01:00
|
|
|
|
2024-05-01 21:27:06 +03:00
|
|
|
|
|
|
|
class ANSIColors:
|
2025-03-21 15:48:10 +01:00
|
|
|
RESET = "\x1b[0m"
|
|
|
|
|
2025-01-03 14:56:24 +02:00
|
|
|
BLACK = "\x1b[30m"
|
2025-03-21 15:48:10 +01:00
|
|
|
BLUE = "\x1b[34m"
|
|
|
|
CYAN = "\x1b[36m"
|
2024-05-01 21:27:06 +03:00
|
|
|
GREEN = "\x1b[32m"
|
2025-05-02 16:06:10 +03:00
|
|
|
GREY = "\x1b[90m"
|
2024-05-01 21:27:06 +03:00
|
|
|
MAGENTA = "\x1b[35m"
|
|
|
|
RED = "\x1b[31m"
|
2025-03-21 15:48:10 +01:00
|
|
|
WHITE = "\x1b[37m" # more like LIGHT GRAY
|
2024-05-01 21:27:06 +03:00
|
|
|
YELLOW = "\x1b[33m"
|
|
|
|
|
2025-05-02 20:22:31 +02:00
|
|
|
BOLD = "\x1b[1m"
|
2025-03-21 15:48:10 +01:00
|
|
|
BOLD_BLACK = "\x1b[1;30m" # DARK GRAY
|
|
|
|
BOLD_BLUE = "\x1b[1;34m"
|
|
|
|
BOLD_CYAN = "\x1b[1;36m"
|
|
|
|
BOLD_GREEN = "\x1b[1;32m"
|
|
|
|
BOLD_MAGENTA = "\x1b[1;35m"
|
|
|
|
BOLD_RED = "\x1b[1;31m"
|
|
|
|
BOLD_WHITE = "\x1b[1;37m" # actual WHITE
|
|
|
|
BOLD_YELLOW = "\x1b[1;33m"
|
|
|
|
|
|
|
|
# intense = like bold but without being bold
|
|
|
|
INTENSE_BLACK = "\x1b[90m"
|
|
|
|
INTENSE_BLUE = "\x1b[94m"
|
|
|
|
INTENSE_CYAN = "\x1b[96m"
|
|
|
|
INTENSE_GREEN = "\x1b[92m"
|
|
|
|
INTENSE_MAGENTA = "\x1b[95m"
|
|
|
|
INTENSE_RED = "\x1b[91m"
|
|
|
|
INTENSE_WHITE = "\x1b[97m"
|
|
|
|
INTENSE_YELLOW = "\x1b[93m"
|
|
|
|
|
|
|
|
BACKGROUND_BLACK = "\x1b[40m"
|
|
|
|
BACKGROUND_BLUE = "\x1b[44m"
|
|
|
|
BACKGROUND_CYAN = "\x1b[46m"
|
|
|
|
BACKGROUND_GREEN = "\x1b[42m"
|
|
|
|
BACKGROUND_MAGENTA = "\x1b[45m"
|
|
|
|
BACKGROUND_RED = "\x1b[41m"
|
|
|
|
BACKGROUND_WHITE = "\x1b[47m"
|
|
|
|
BACKGROUND_YELLOW = "\x1b[43m"
|
|
|
|
|
|
|
|
INTENSE_BACKGROUND_BLACK = "\x1b[100m"
|
|
|
|
INTENSE_BACKGROUND_BLUE = "\x1b[104m"
|
|
|
|
INTENSE_BACKGROUND_CYAN = "\x1b[106m"
|
|
|
|
INTENSE_BACKGROUND_GREEN = "\x1b[102m"
|
|
|
|
INTENSE_BACKGROUND_MAGENTA = "\x1b[105m"
|
|
|
|
INTENSE_BACKGROUND_RED = "\x1b[101m"
|
|
|
|
INTENSE_BACKGROUND_WHITE = "\x1b[107m"
|
|
|
|
INTENSE_BACKGROUND_YELLOW = "\x1b[103m"
|
|
|
|
|
2024-05-01 21:27:06 +03:00
|
|
|
|
2025-05-02 16:06:10 +03:00
|
|
|
ColorCodes = set()
|
2024-05-01 21:27:06 +03:00
|
|
|
NoColors = ANSIColors()
|
|
|
|
|
2025-05-02 16:06:10 +03:00
|
|
|
for attr, code in ANSIColors.__dict__.items():
|
2024-05-01 21:27:06 +03:00
|
|
|
if not attr.startswith("__"):
|
2025-05-02 16:06:10 +03:00
|
|
|
ColorCodes.add(code)
|
2024-05-01 21:27:06 +03:00
|
|
|
setattr(NoColors, attr, "")
|
|
|
|
|
|
|
|
|
2025-03-21 15:48:10 +01:00
|
|
|
def get_colors(
|
|
|
|
colorize: bool = False, *, file: IO[str] | IO[bytes] | None = None
|
|
|
|
) -> ANSIColors:
|
2025-01-20 12:52:42 +02:00
|
|
|
if colorize or can_colorize(file=file):
|
2024-05-01 21:27:06 +03:00
|
|
|
return ANSIColors()
|
|
|
|
else:
|
|
|
|
return NoColors
|
|
|
|
|
|
|
|
|
2025-05-02 16:06:10 +03:00
|
|
|
def decolor(text: str) -> str:
|
|
|
|
"""Remove ANSI color codes from a string."""
|
|
|
|
for code in ColorCodes:
|
|
|
|
text = text.replace(code, "")
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
2025-03-21 15:48:10 +01:00
|
|
|
def can_colorize(*, file: IO[str] | IO[bytes] | None = None) -> bool:
|
2025-01-20 12:52:42 +02:00
|
|
|
if file is None:
|
|
|
|
file = sys.stdout
|
|
|
|
|
2024-05-01 21:27:06 +03:00
|
|
|
if not sys.flags.ignore_environment:
|
|
|
|
if os.environ.get("PYTHON_COLORS") == "0":
|
|
|
|
return False
|
|
|
|
if os.environ.get("PYTHON_COLORS") == "1":
|
|
|
|
return True
|
2025-01-27 16:24:10 +02:00
|
|
|
if os.environ.get("NO_COLOR"):
|
2025-01-21 18:10:08 +02:00
|
|
|
return False
|
2024-05-01 21:27:06 +03:00
|
|
|
if not COLORIZE:
|
|
|
|
return False
|
2025-01-27 16:24:10 +02:00
|
|
|
if os.environ.get("FORCE_COLOR"):
|
2025-01-21 18:10:08 +02:00
|
|
|
return True
|
|
|
|
if os.environ.get("TERM") == "dumb":
|
|
|
|
return False
|
2024-05-01 21:27:06 +03:00
|
|
|
|
2025-01-20 12:52:42 +02:00
|
|
|
if not hasattr(file, "fileno"):
|
2024-05-01 21:27:06 +03:00
|
|
|
return False
|
|
|
|
|
2024-12-14 22:25:49 +07:00
|
|
|
if sys.platform == "win32":
|
|
|
|
try:
|
|
|
|
import nt
|
|
|
|
|
|
|
|
if not nt._supports_virtual_terminal():
|
|
|
|
return False
|
|
|
|
except (ImportError, AttributeError):
|
|
|
|
return False
|
|
|
|
|
2024-05-01 21:27:06 +03:00
|
|
|
try:
|
2025-01-20 12:52:42 +02:00
|
|
|
return os.isatty(file.fileno())
|
2024-05-01 21:27:06 +03:00
|
|
|
except io.UnsupportedOperation:
|
2025-03-21 15:48:10 +01:00
|
|
|
return hasattr(file, "isatty") and file.isatty()
|
2025-05-02 20:22:31 +02:00
|
|
|
|
|
|
|
|
|
|
|
def set_theme(t: dict[ColorTag, str] | None = None) -> None:
|
|
|
|
global theme
|
|
|
|
|
|
|
|
if t:
|
|
|
|
theme = t
|
|
|
|
return
|
|
|
|
|
|
|
|
colors = get_colors()
|
|
|
|
theme = {
|
|
|
|
"PROMPT": colors.BOLD_MAGENTA,
|
|
|
|
"KEYWORD": colors.BOLD_BLUE,
|
|
|
|
"BUILTIN": colors.CYAN,
|
|
|
|
"COMMENT": colors.RED,
|
|
|
|
"STRING": colors.GREEN,
|
|
|
|
"NUMBER": colors.YELLOW,
|
|
|
|
"OP": colors.RESET,
|
|
|
|
"DEFINITION": colors.BOLD,
|
|
|
|
"SOFT_KEYWORD": colors.BOLD_BLUE,
|
|
|
|
"RESET": colors.RESET,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
set_theme()
|