From 70863bcc59811dfc50f53b049001b28aa9bbf262 Mon Sep 17 00:00:00 2001 From: Marcel R Date: Sat, 30 Sep 2023 11:49:25 +0200 Subject: [PATCH] Colored repr's. --- order/models/base.py | 13 +++-- order/types.py | 2 +- order/util.py | 126 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 135 insertions(+), 6 deletions(-) diff --git a/order/models/base.py b/order/models/base.py index 78f3171..2f1be8e 100644 --- a/order/models/base.py +++ b/order/models/base.py @@ -14,7 +14,7 @@ from order.types import Any, GeneratorType, Field, FieldInfo, Lazy from order.adapters.base import AdapterModel, DataProvider -from order.util import no_value +from order.util import no_value, colored class ModelMeta(type(BaseModel)): @@ -160,11 +160,15 @@ class Model(BaseModel, metaclass=ModelMeta): Base model for all order entities. """ + def __repr_name__(self) -> str: + return colored(super().__repr_name__(), color="light_green") + def __repr_args__(self) -> GeneratorType: """ Yields all key-values pairs to be injected into the representation. """ - yield from super().__repr_args__() + for attr, value in super().__repr_args__(): + yield colored(attr, color="light_blue"), value for attr, lazy_attr in self._lazy_attrs.items(): # skip when field was originally skipped @@ -173,4 +177,7 @@ def __repr_args__(self) -> GeneratorType: continue value = getattr(self, lazy_attr) - yield attr, f"lazy({value.adapter})" if isinstance(value, AdapterModel) else value + yield ( + colored(attr, color="light_blue"), + f"lazy({value.adapter})" if isinstance(value, AdapterModel) else value, + ) diff --git a/order/types.py b/order/types.py index f35718d..d302422 100644 --- a/order/types.py +++ b/order/types.py @@ -13,7 +13,7 @@ import re from collections.abc import KeysView, ValuesView # noqa -from typing import Any, Union, TypeVar, ClassVar, List, Tuple, Sequence, Set, Dict # noqa +from typing import Any, Union, TypeVar, ClassVar, List, Tuple, Sequence, Set, Dict, Callable # noqa from types import GeneratorType # noqa from typing_extensions import Annotated, _AnnotatedAlias as AnnotatedType # noqa diff --git a/order/util.py b/order/util.py index 779eef0..583453e 100644 --- a/order/util.py +++ b/order/util.py @@ -7,17 +7,139 @@ from __future__ import annotations -__all__ = ["no_value", "create_hash", "DotAccessProxy"] +__all__ = ["no_value", "colored", "uncolored", "create_hash", "DotAccessProxy"] +import os +import sys +import re +import random import hashlib -from typing import Any, Callable + +try: + import ipykernel + import ipykernel.iostream +except ImportError: + ipykernel = None + +from order.types import Any, Callable #: Unique object denoting *no value*. no_value = object() +# terminal codes for colors +colors = { + "default": 39, + "black": 30, + "red": 31, + "green": 32, + "yellow": 33, + "blue": 34, + "magenta": 35, + "cyan": 36, + "light_gray": 37, + "dark_gray": 90, + "light_red": 91, + "light_green": 92, + "light_yellow": 93, + "light_blue": 94, + "light_magenta": 95, + "light_cyan": 96, + "white": 97, +} + +# terminal codes for backgrounds +backgrounds = { + "default": 49, + "black": 40, + "red": 41, + "green": 42, + "yellow": 43, + "blue": 44, + "magenta": 45, + "cyan": 46, + "light_gray": 47, + "dark_gray": 100, + "light_red": 101, + "light_green": 102, + "light_yellow": 103, + "light_blue": 104, + "light_magenta": 105, + "light_cyan": 106, + "white": 107, +} + +# terminal codes for styles +styles = { + "default": 0, + "bright": 1, + "dim": 2, + "underlined": 4, + "blink": 5, + "inverted": 7, + "hidden": 8, +} + + +def colored(msg, color=None, background=None, style=None, force=False): + """ + Return the colored version of a string *msg*. For *color*, *background* and *style* options, see + https://misc.flogisoft.com/bash/tip_colors_and_formatting. They can also be explicitely set to + ``"random"`` to get a random value. Unless *force* is *True*, the *msg* string is returned + unchanged in case the output is neither a tty nor an IPython output stream. + """ + if not force: + tty = False + ipy = False + + try: + tty = os.isatty(sys.stdout.fileno()) + except: + pass + + if not tty and ipykernel is not None: + ipy = isinstance(sys.stdout, ipykernel.iostream.OutStream) + + if not tty and not ipy: + return msg + + # get the color + if color == "random": + color = random.choice(list(colors.values())) + else: + color = colors.get(color, colors["default"]) + + # get the background + if background == "random": + background = random.choice(list(backgrounds.values())) + else: + background = backgrounds.get(background, backgrounds["default"]) + + # get multiple styles + if not isinstance(style, (tuple, list, set)): + style = (style,) + style_values = list(styles.values()) + style = ";".join( + str(random.choice(style_values) if s == "random" else styles.get(s, styles["default"])) + for s in style + ) + + return f"\033[{style};{background};{color}m{msg}\033[0m" + + +# compiled regular expression for removing all terminal style codes +uncolor_cre = re.compile(r"(\x1B\[[0-?]*[ -/]*[@-~])") + + +def uncolored(s): + """ + Removes all terminal style codes from a string *s* and returns it. + """ + return uncolor_cre.sub("", s) + + def create_hash(inp: Any, l: int = 10, algo: str = "sha256", to_int: bool = False) -> str | int: """ Takes an arbitrary input *inp* and creates a hexadecimal string hash based on an algorithm