1
0
mirror of https://github.com/httpie/cli.git synced 2024-11-21 17:16:30 +02:00

Refactor the color system

This commit is contained in:
Batuhan Taskaya 2022-05-10 10:37:47 +03:00
parent 8173cb0337
commit ad5f01635b
9 changed files with 175 additions and 162 deletions

View File

@ -18,7 +18,7 @@ from .config import DEFAULT_CONFIG_DIR, Config, ConfigFileError
from .encoding import UTF8
from .utils import repr_dict
from .output.ui.palette import GenericColor
from .output.ui.palette import RichColor
if TYPE_CHECKING:
from rich.console import Console
@ -31,9 +31,9 @@ class LogLevel(str, Enum):
LOG_LEVEL_COLORS = {
LogLevel.INFO: GenericColor.PINK,
LogLevel.WARNING: GenericColor.ORANGE,
LogLevel.ERROR: GenericColor.RED,
LogLevel.INFO: RichColor.PINK,
LogLevel.WARNING: RichColor.ORANGE,
LogLevel.ERROR: RichColor.RED,
}
LOG_LEVEL_DISPLAY_THRESHOLDS = {
@ -191,10 +191,10 @@ class Environment:
force_terminal: bool
) -> 'Console':
from rich.console import Console
from httpie.output.ui.rich_palette import _make_rich_color_theme
from httpie.output.ui.palette import make_rich_theme_from_style
style = getattr(self.args, 'style', None)
theme = _make_rich_color_theme(style)
theme = make_rich_theme_from_style(style)
# Rich infers the rest of the knowledge (e.g encoding)
# dynamically by looking at the file/stderr.
return Console(

View File

@ -0,0 +1,3 @@
from httpie.output.ui.palette.pie import AUTO_STYLE, SHADE_TO_PIE_STYLE, PieStyle, PieColor, ColorString, get_color # noqa
from httpie.output.ui.palette.rich import RichColor, make_rich_theme_from_style
from httpie.output.ui.palette.utils import ColorString

View File

@ -0,0 +1,18 @@
from httpie.output.ui.palette.rich import RichColor
from httpie.output.ui.palette.utils import ColorString
RICH_BOLD = ColorString('bold')
# Rich-specific color code declarations
# <https://github.com/Textualize/rich/blob/fcd684dd3a482977cab620e71ccaebb94bf13ac9/rich/default_styles.py>
RICH_CUSTOM_STYLES = {
'progress.description': RICH_BOLD | RichColor.WHITE,
'progress.data.speed': RICH_BOLD | RichColor.GREEN,
'progress.percentage': RICH_BOLD | RichColor.AQUA,
'progress.download': RICH_BOLD | RichColor.AQUA,
'progress.remaining': RICH_BOLD | RichColor.ORANGE,
'bar.complete': RICH_BOLD | RichColor.PURPLE,
'bar.finished': RICH_BOLD | RichColor.GREEN,
'bar.pulse': RICH_BOLD | RichColor.PURPLE,
'option': RICH_BOLD | RichColor.PINK,
}

View File

@ -1,18 +1,12 @@
from dataclasses import dataclass, field
from enum import Enum, auto
from typing import Optional, List
from enum import Enum
from typing import Optional
from httpie.output.ui.palette.utils import ColorString
PYGMENTS_BRIGHT_BLACK = 'ansibrightblack'
AUTO_STYLE = 'auto' # Follows terminal ANSI color styles
class Styles(Enum):
PIE = auto()
ANSI = auto()
class PieStyle(str, Enum):
UNIVERSAL = 'pie'
DARK = 'pie-dark'
@ -24,34 +18,11 @@ PIE_STYLE_TO_SHADE = {
PieStyle.UNIVERSAL: '600',
PieStyle.LIGHT: '700',
}
SHADE_TO_PIE_STYLE = {
shade: style for style, shade in PIE_STYLE_TO_SHADE.items()
}
class ColorString(str):
def __or__(self, other: str) -> 'ColorString':
"""Combine a style with a property.
E.g: PieColor.BLUE | BOLD | ITALIC
"""
if isinstance(other, str):
# In case of PieColor.BLUE | SOMETHING
# we just create a new string.
return ColorString(self + ' ' + other)
elif isinstance(other, GenericColor):
# If we see a GenericColor, then we'll wrap it
# in with the desired property in a different class.
return _StyledGenericColor(other, styles=self.split())
elif isinstance(other, _StyledGenericColor):
# And if it is already wrapped, we'll just extend the
# list of properties.
other.styles.extend(self.split())
return other
else:
return NotImplemented
class PieColor(ColorString, Enum):
"""Styles that are available only in Pie themes."""
@ -71,42 +42,6 @@ class PieColor(ColorString, Enum):
YELLOW = 'yellow'
class GenericColor(Enum):
"""Generic colors that are safe to use everywhere."""
# <https://rich.readthedocs.io/en/stable/appendix/colors.html>
WHITE = {Styles.PIE: PieColor.WHITE, Styles.ANSI: 'white'}
BLACK = {Styles.PIE: PieColor.BLACK, Styles.ANSI: 'black'}
GREEN = {Styles.PIE: PieColor.GREEN, Styles.ANSI: 'green'}
ORANGE = {Styles.PIE: PieColor.ORANGE, Styles.ANSI: 'yellow'}
YELLOW = {Styles.PIE: PieColor.YELLOW, Styles.ANSI: 'bright_yellow'}
BLUE = {Styles.PIE: PieColor.BLUE, Styles.ANSI: 'blue'}
PINK = {Styles.PIE: PieColor.PINK, Styles.ANSI: 'bright_magenta'}
PURPLE = {Styles.PIE: PieColor.PURPLE, Styles.ANSI: 'magenta'}
RED = {Styles.PIE: PieColor.RED, Styles.ANSI: 'red'}
AQUA = {Styles.PIE: PieColor.AQUA, Styles.ANSI: 'cyan'}
GREY = {Styles.PIE: PieColor.GREY, Styles.ANSI: 'bright_black'}
def apply_style(
self, style: Styles, *, style_name: Optional[str] = None
) -> str:
"""Apply the given style to a particular value."""
exposed_color = self.value[style]
if style is Styles.PIE:
assert style_name is not None
shade = PIE_STYLE_TO_SHADE[PieStyle(style_name)]
return get_color(exposed_color, shade)
else:
return exposed_color
@dataclass
class _StyledGenericColor:
color: 'GenericColor'
styles: List[str] = field(default_factory=list)
# noinspection PyDictCreation
COLOR_PALETTE = {
# Copy the brand palette
@ -252,10 +187,6 @@ COLOR_PALETTE.update(
)
def boldify(color: PieColor) -> str:
return f'bold {color}'
# noinspection PyDefaultArgument
def get_color(
color: PieColor, shade: str, *, palette=COLOR_PALETTE

View File

@ -0,0 +1,111 @@
from collections import ChainMap
from typing import TYPE_CHECKING, Any, Optional, List
from enum import Enum, auto
from dataclasses import dataclass, field
if TYPE_CHECKING:
from rich.theme import Theme
from httpie.output.ui.palette.pie import (
PIE_STYLE_TO_SHADE,
PieStyle,
PieColor,
get_color,
) # noqa
class RichTheme(Enum):
"""Represents the color theme to use within rich."""
PIE = auto()
ANSI = auto()
@classmethod
def from_style_name(cls, style_name: str) -> 'RichTheme':
try:
PieStyle(style_name)
except ValueError:
return RichTheme.ANSI
else:
return RichTheme.PIE
class RichColor(Enum):
"""Generic colors that are safe to use everywhere within rich."""
# <https://rich.readthedocs.io/en/stable/appendix/colors.html>
WHITE = {RichTheme.PIE: PieColor.WHITE, RichTheme.ANSI: 'white'}
BLACK = {RichTheme.PIE: PieColor.BLACK, RichTheme.ANSI: 'black'}
GREEN = {RichTheme.PIE: PieColor.GREEN, RichTheme.ANSI: 'green'}
ORANGE = {RichTheme.PIE: PieColor.ORANGE, RichTheme.ANSI: 'yellow'}
YELLOW = {RichTheme.PIE: PieColor.YELLOW, RichTheme.ANSI: 'bright_yellow'}
BLUE = {RichTheme.PIE: PieColor.BLUE, RichTheme.ANSI: 'blue'}
PINK = {RichTheme.PIE: PieColor.PINK, RichTheme.ANSI: 'bright_magenta'}
PURPLE = {RichTheme.PIE: PieColor.PURPLE, RichTheme.ANSI: 'magenta'}
RED = {RichTheme.PIE: PieColor.RED, RichTheme.ANSI: 'red'}
AQUA = {RichTheme.PIE: PieColor.AQUA, RichTheme.ANSI: 'cyan'}
GREY = {RichTheme.PIE: PieColor.GREY, RichTheme.ANSI: 'bright_black'}
def apply_theme(
self, style: RichTheme, *, style_name: Optional[str] = None
) -> str:
"""Apply the given style to a particular value."""
exposed_color = self.value[style]
if style is RichTheme.PIE:
assert style_name is not None
shade = PIE_STYLE_TO_SHADE[PieStyle(style_name)]
return get_color(exposed_color, shade)
else:
return exposed_color
@dataclass
class _StyledRichColor:
color: RichColor
styles: List[str] = field(default_factory=list)
class _RichColorCaster(dict):
"""
Translate RichColor to a regular string on the attribute access
phase.
"""
def _translate(self, key: Any) -> Any:
if isinstance(key, RichColor):
return key.name.lower()
else:
return key
def __getitem__(self, key: Any) -> Any:
return super().__getitem__(self._translate(key))
def get(self, key: Any) -> Any:
return super().get(self._translate(key))
def make_rich_theme_from_style(style_name: Optional[str] = None) -> 'Theme':
from rich.style import Style
from rich.theme import Theme
from httpie.output.ui.palette.custom_styles import RICH_CUSTOM_STYLES
rich_theme = RichTheme.from_style_name(style_name)
theme = Theme()
for color, color_set in ChainMap(
RichColor.__members__, RICH_CUSTOM_STYLES
).items():
if isinstance(color_set, _StyledRichColor):
properties = dict.fromkeys(color_set.styles, True)
color_set = color_set.color
else:
properties = {}
theme.styles[color.lower()] = Style(
color=color_set.apply_theme(rich_theme, style_name=style_name),
**properties,
)
# E.g translate RichColor.BLUE into blue on key access
theme.styles = _RichColorCaster(theme.styles)
return theme

View File

@ -0,0 +1,23 @@
class ColorString(str):
def __or__(self, other: str) -> 'ColorString':
"""Combine a style with a property.
E.g: PieColor.BLUE | BOLD | ITALIC
"""
from httpie.output.ui.palette.rich import RichColor, _StyledRichColor
if isinstance(other, str):
# In case of PieColor.BLUE | SOMETHING
# we just create a new string.
return ColorString(self + ' ' + other)
elif isinstance(other, RichColor):
# If we see a GenericColor, then we'll wrap it
# in with the desired property in a different class.
return _StyledRichColor(other, styles=self.split())
elif isinstance(other, _StyledRichColor):
# And if it is already wrapped, we'll just extend the
# list of properties.
other.styles.extend(self.split())
return other
else:
return NotImplemented

View File

@ -10,17 +10,17 @@ from rich.text import Text
from httpie.cli.constants import SEPARATOR_GROUP_ALL_ITEMS
from httpie.cli.options import Argument, ParserSpec, Qualifiers
from httpie.output.ui.palette import GenericColor
from httpie.output.ui.palette import RichColor
SEPARATORS = '|'.join(map(re.escape, SEPARATOR_GROUP_ALL_ITEMS))
STYLE_METAVAR = GenericColor.YELLOW
STYLE_SWITCH = GenericColor.GREEN
STYLE_PROGRAM_NAME = GenericColor.GREEN # .boldify()
STYLE_USAGE_OPTIONAL = GenericColor.GREY
STYLE_USAGE_REGULAR = GenericColor.WHITE
STYLE_USAGE_ERROR = GenericColor.RED
STYLE_USAGE_MISSING = GenericColor.YELLOW
STYLE_METAVAR = RichColor.YELLOW
STYLE_SWITCH = RichColor.GREEN
STYLE_PROGRAM_NAME = RichColor.GREEN # .boldify()
STYLE_USAGE_OPTIONAL = RichColor.GREY
STYLE_USAGE_REGULAR = RichColor.WHITE
STYLE_USAGE_ERROR = RichColor.RED
STYLE_USAGE_MISSING = RichColor.YELLOW
STYLE_BOLD = 'bold'
MAX_CHOICE_CHARS = 80

View File

@ -1,73 +0,0 @@
from collections import ChainMap
from typing import TYPE_CHECKING, Any, Optional
if TYPE_CHECKING:
from rich.theme import Theme
from httpie.output.ui.palette import GenericColor, PieStyle, Styles, ColorString, _StyledGenericColor # noqa
RICH_BOLD = ColorString('bold')
# Rich-specific color code declarations
# <https://github.com/Textualize/rich/blob/fcd684dd3a482977cab620e71ccaebb94bf13ac9/rich/default_styles.py>
CUSTOM_STYLES = {
'progress.description': RICH_BOLD | GenericColor.WHITE,
'progress.data.speed': RICH_BOLD | GenericColor.GREEN,
'progress.percentage': RICH_BOLD | GenericColor.AQUA,
'progress.download': RICH_BOLD | GenericColor.AQUA,
'progress.remaining': RICH_BOLD | GenericColor.ORANGE,
'bar.complete': RICH_BOLD | GenericColor.PURPLE,
'bar.finished': RICH_BOLD | GenericColor.GREEN,
'bar.pulse': RICH_BOLD | GenericColor.PURPLE,
'option': RICH_BOLD | GenericColor.PINK,
}
class _GenericColorCaster(dict):
"""
Translate GenericColor to a regular string on the attribute access
phase.
"""
def _translate(self, key: Any) -> Any:
if isinstance(key, GenericColor):
return key.name.lower()
else:
return key
def __getitem__(self, key: Any) -> Any:
return super().__getitem__(self._translate(key))
def get(self, key: Any) -> Any:
return super().get(self._translate(key))
def _make_rich_color_theme(style_name: Optional[str] = None) -> 'Theme':
from rich.style import Style
from rich.theme import Theme
try:
PieStyle(style_name)
except ValueError:
style = Styles.ANSI
else:
style = Styles.PIE
theme = Theme()
for color, color_set in ChainMap(
GenericColor.__members__, CUSTOM_STYLES
).items():
if isinstance(color_set, _StyledGenericColor):
properties = dict.fromkeys(color_set.styles, True)
color_set = color_set.color
else:
properties = {}
theme.styles[color.lower()] = Style(
color=color_set.apply_style(style, style_name=style_name),
**properties,
)
# E.g translate GenericColor.BLUE into blue on key access
theme.styles = _GenericColorCaster(theme.styles)
return theme

View File

@ -6,7 +6,7 @@ from contextlib import contextmanager
from rich.console import Console, RenderableType
from rich.highlighter import Highlighter
from httpie.output.ui.rich_palette import _make_rich_color_theme
from httpie.output.ui.palette import make_rich_theme_from_style
def render_as_string(renderable: RenderableType) -> str:
@ -14,7 +14,7 @@ def render_as_string(renderable: RenderableType) -> str:
return a *style-less* version of it as a string."""
with open(os.devnull, 'w') as null_stream:
fake_console = Console(file=null_stream, record=True, theme=_make_rich_color_theme())
fake_console = Console(file=null_stream, record=True, theme=make_rich_theme_from_style())
fake_console.print(renderable)
return fake_console.export_text()