diff options
author | Batuhan Taskaya <isidentical@gmail.com> | 2022-05-10 10:37:47 +0300 |
---|---|---|
committer | Batuhan Taskaya <isidentical@gmail.com> | 2022-05-10 10:51:31 +0300 |
commit | ad5f01635b7b06068b55dec72ff3dc35d38a9356 (patch) | |
tree | dfbaa17a08672ce6f37fe78d47960589796a004d | |
parent | 8173cb03378ae05239eedfca86baa2dbb492c26e (diff) |
Refactor the color systemisidentical/refactors/color-system
-rw-r--r-- | httpie/context.py | 12 | ||||
-rw-r--r-- | httpie/output/ui/palette/__init__.py | 3 | ||||
-rw-r--r-- | httpie/output/ui/palette/custom_styles.py | 18 | ||||
-rw-r--r-- | httpie/output/ui/palette/pie.py (renamed from httpie/output/ui/palette.py) | 77 | ||||
-rw-r--r-- | httpie/output/ui/palette/rich.py | 111 | ||||
-rw-r--r-- | httpie/output/ui/palette/utils.py | 23 | ||||
-rw-r--r-- | httpie/output/ui/rich_help.py | 16 | ||||
-rw-r--r-- | httpie/output/ui/rich_palette.py | 73 | ||||
-rw-r--r-- | httpie/output/ui/rich_utils.py | 4 |
9 files changed, 175 insertions, 162 deletions
diff --git a/httpie/context.py b/httpie/context.py index 086cfa4f..febf8c2e 100644 --- a/httpie/context.py +++ b/httpie/context.py @@ -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( diff --git a/httpie/output/ui/palette/__init__.py b/httpie/output/ui/palette/__init__.py new file mode 100644 index 00000000..e82b21fd --- /dev/null +++ b/httpie/output/ui/palette/__init__.py @@ -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 diff --git a/httpie/output/ui/palette/custom_styles.py b/httpie/output/ui/palette/custom_styles.py new file mode 100644 index 00000000..bc51bd7b --- /dev/null +++ b/httpie/output/ui/palette/custom_styles.py @@ -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, +} diff --git a/httpie/output/ui/palette.py b/httpie/output/ui/palette/pie.py index 5666b21f..bceab589 100644 --- a/httpie/output/ui/palette.py +++ b/httpie/output/ui/palette/pie.py @@ -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 diff --git a/httpie/output/ui/palette/rich.py b/httpie/output/ui/palette/rich.py new file mode 100644 index 00000000..ecc1799f --- /dev/null +++ b/httpie/output/ui/palette/rich.py @@ -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 diff --git a/httpie/output/ui/palette/utils.py b/httpie/output/ui/palette/utils.py new file mode 100644 index 00000000..47144cbc --- /dev/null +++ b/httpie/output/ui/palette/utils.py @@ -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 diff --git a/httpie/output/ui/rich_help.py b/httpie/output/ui/rich_help.py index 2b19f3e3..354d3b2a 100644 --- a/httpie/output/ui/rich_help.py +++ b/httpie/output/ui/rich_help.py @@ -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 diff --git a/httpie/output/ui/rich_palette.py b/httpie/output/ui/rich_palette.py deleted file mode 100644 index 103d4637..00000000 --- a/httpie/output/ui/rich_palette.py +++ /dev/null @@ -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 diff --git a/httpie/output/ui/rich_utils.py b/httpie/output/ui/rich_utils.py index 4843d149..77c34097 100644 --- a/httpie/output/ui/rich_utils.py +++ b/httpie/output/ui/rich_utils.py @@ -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() |