from enum import IntEnum from .utils import isatty __all__ = 'sformat', 'sprint' MYPY = False if MYPY: from typing import Any, Union def strip_ansi(value): import re return re.sub('\033\\[((?:\\d|;)*)([a-zA-Z])', '', value) class Style(IntEnum): reset = 0 bold = 1 not_bold = 22 dim = 2 not_dim = 22 italic = 3 not_italic = 23 underline = 4 not_underline = 24 blink = 5 not_blink = 25 reverse = 7 not_reverse = 27 strike_through = 9 not_strike_through = 29 # foreground colours black = 30 red = 31 green = 32 yellow = 33 blue = 34 magenta = 35 cyan = 36 white = 37 # background colours bg_black = 40 bg_red = 41 bg_green = 42 bg_yellow = 43 bg_blue = 44 bg_magenta = 45 bg_cyan = 46 bg_white = 47 # this is a meta value used for the "Style" instance which is the "style" function function = -1 def __call__(self, input: 'Any', *styles: 'Style', reset: bool = True, apply: bool = True) -> str: """ Styles text with ANSI styles and returns the new string. By default the styling is cleared at the end of the string, this can be prevented with``reset=False``. Examples:: print(sformat('Hello World!', sformat.green)) print(sformat('ATTENTION!', sformat.bg_magenta)) print(sformat('Some things', sformat.reverse, sformat.bold)) :param input: the object to style with ansi codes. :param *styles: zero or more styles to apply to the text, should be either style instances or strings matching style names. :param reset: if False the ansi reset code is not appended to the end of the string :param: apply: if False no ansi codes are applied """ text = str(input) if not apply: return text codes = [] for s in styles: # raw ints are allowed if not isinstance(s, self.__class__) and not isinstance(s, int): try: s = self.styles[s] except KeyError: raise ValueError(f'invalid style "{s}"') codes.append(_style_as_int(s.value)) if codes: r = _as_ansi(';'.join(codes)) + text else: r = text if reset: r += _as_ansi(_style_as_int(self.reset)) return r @property def styles(self): return self.__class__.__members__ def __repr__(self): if self == self.function: return '' else: return super().__repr__() def __str__(self): if self == self.function: return repr(self) else: return super().__str__() def _style_as_int(v: 'Union[Style, int]') -> str: if isinstance(v, Style): return str(v.value) else: return str(v) def _as_ansi(s: str) -> str: return f'\033[{s}m' sformat = Style(-1) class StylePrint: """ Annoyingly enums do not allow inheritance, a lazy design mistake, this is an ugly work around for that mistake. """ def __call__(self, input, *styles, reset=True, flush=True, file=None, **print_kwargs): text = sformat(input, *styles, reset=reset, apply=isatty(file)) print(text, flush=flush, file=file, **print_kwargs) def __getattr__(self, item): return getattr(sformat, item) def __repr__(self): return '' sprint = StylePrint()