mirror of
https://github.com/beetbox/beets.git
synced 2025-12-15 21:14:19 +01:00
Slightly simplify colors setup
This replaces the funky color setup based on a global `COLORS` variable with a cached function `get_color_config`.
This commit is contained in:
parent
0818505334
commit
04380676e1
1 changed files with 38 additions and 53 deletions
|
|
@ -30,7 +30,9 @@ import textwrap
|
|||
import traceback
|
||||
import warnings
|
||||
from difflib import SequenceMatcher
|
||||
from typing import Any, Callable
|
||||
from functools import cache
|
||||
from itertools import chain
|
||||
from typing import Any, Callable, Literal
|
||||
|
||||
import confuse
|
||||
|
||||
|
|
@ -463,7 +465,7 @@ LEGACY_COLORS = {
|
|||
"white": ["bold", "white"],
|
||||
}
|
||||
# All ANSI Colors.
|
||||
ANSI_CODES = {
|
||||
CODE_BY_COLOR = {
|
||||
# Styles.
|
||||
"normal": 0,
|
||||
"bold": 1,
|
||||
|
|
@ -496,9 +498,7 @@ ANSI_CODES = {
|
|||
}
|
||||
RESET_COLOR = f"{COLOR_ESCAPE}39;49;00m"
|
||||
|
||||
# These abstract COLOR_NAMES are lazily mapped on to the actual color in COLORS
|
||||
# as they are defined in the configuration files, see function: colorize
|
||||
COLOR_NAMES = [
|
||||
ColorName = Literal[
|
||||
"text_success",
|
||||
"text_warning",
|
||||
"text_error",
|
||||
|
|
@ -515,61 +515,46 @@ COLOR_NAMES = [
|
|||
"text_diff_added",
|
||||
"text_diff_removed",
|
||||
]
|
||||
COLORS: dict[str, list[str]] | None = None
|
||||
|
||||
|
||||
def _colorize(color, text):
|
||||
"""Returns a string that prints the given text in the given color
|
||||
in a terminal that is ANSI color-aware. The color must be a list of strings
|
||||
from ANSI_CODES.
|
||||
@cache
|
||||
def get_color_config() -> dict[ColorName, str]:
|
||||
"""Parse and validate color configuration, converting names to ANSI codes.
|
||||
|
||||
Processes the UI color configuration, handling both new list format and
|
||||
legacy single-color format. Validates all color names against known codes
|
||||
and raises an error for any invalid entries.
|
||||
"""
|
||||
# Construct escape sequence to be put before the text by iterating
|
||||
# over all "ANSI codes" in `color`.
|
||||
escape = ""
|
||||
for code in color:
|
||||
escape = f"{escape}{COLOR_ESCAPE}{ANSI_CODES[code]}m"
|
||||
return f"{escape}{text}{RESET_COLOR}"
|
||||
colors_by_color_name: dict[ColorName, list[str]] = {
|
||||
k: (v if isinstance(v, list) else LEGACY_COLORS.get(v, [v]))
|
||||
for k, v in config["ui"]["colors"].flatten().items()
|
||||
}
|
||||
|
||||
if invalid_colors := (
|
||||
set(chain.from_iterable(colors_by_color_name.values()))
|
||||
- CODE_BY_COLOR.keys()
|
||||
):
|
||||
raise UserError(
|
||||
f"Invalid color(s) in configuration: {', '.join(invalid_colors)}"
|
||||
)
|
||||
|
||||
return {
|
||||
n: ";".join(str(CODE_BY_COLOR[c]) for c in colors)
|
||||
for n, colors in colors_by_color_name.items()
|
||||
}
|
||||
|
||||
|
||||
def colorize(color_name, text):
|
||||
"""Colorize text if colored output is enabled. (Like _colorize but
|
||||
conditional.)
|
||||
def colorize(color_name: ColorName, text: str) -> str:
|
||||
"""Apply ANSI color formatting to text based on configuration settings.
|
||||
|
||||
Returns colored text when color output is enabled and NO_COLOR environment
|
||||
variable is not set, otherwise returns plain text unchanged.
|
||||
"""
|
||||
if config["ui"]["color"] and "NO_COLOR" not in os.environ:
|
||||
global COLORS
|
||||
if not COLORS:
|
||||
# Read all color configurations and set global variable COLORS.
|
||||
COLORS = dict()
|
||||
for name in COLOR_NAMES:
|
||||
# Convert legacy color definitions (strings) into the new
|
||||
# list-based color definitions. Do this by trying to read the
|
||||
# color definition from the configuration as unicode - if this
|
||||
# is successful, the color definition is a legacy definition
|
||||
# and has to be converted.
|
||||
try:
|
||||
color_def = config["ui"]["colors"][name].get(str)
|
||||
except (confuse.ConfigTypeError, NameError):
|
||||
# Normal color definition (type: list of unicode).
|
||||
color_def = config["ui"]["colors"][name].get(list)
|
||||
else:
|
||||
# Legacy color definition (type: unicode). Convert.
|
||||
if color_def in LEGACY_COLORS:
|
||||
color_def = LEGACY_COLORS[color_def]
|
||||
else:
|
||||
raise UserError("no such color %s", color_def)
|
||||
for code in color_def:
|
||||
if code not in ANSI_CODES.keys():
|
||||
raise ValueError("no such ANSI code %s", code)
|
||||
COLORS[name] = color_def
|
||||
# In case a 3rd party plugin is still passing the actual color ('red')
|
||||
# instead of the abstract color name ('text_error')
|
||||
color = COLORS.get(color_name)
|
||||
if not color:
|
||||
log.debug("Invalid color_name: {}", color_name)
|
||||
color = color_name
|
||||
return _colorize(color, text)
|
||||
else:
|
||||
return text
|
||||
color_code = get_color_config()[color_name]
|
||||
return f"{COLOR_ESCAPE}{color_code}m{text}{RESET_COLOR}"
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def uncolorize(colored_text):
|
||||
|
|
|
|||
Loading…
Reference in a new issue