diff --git a/eris/__main__.py b/eris/__main__.py index 600bbf1..51439b9 100755 --- a/eris/__main__.py +++ b/eris/__main__.py @@ -41,7 +41,7 @@ import eris.worker as worker import eris.paged_list as paged_list import fill3 import sorted_collection -import termstr.termstr as termstr +import termstr import termstr.terminal as terminal diff --git a/eris/pydoc_color.py b/eris/pydoc_color.py index 601f32c..9955978 100755 --- a/eris/pydoc_color.py +++ b/eris/pydoc_color.py @@ -4,7 +4,7 @@ import pydoc import sys -import termstr.termstr as termstr +import termstr class TermDoc(pydoc.TextDoc): diff --git a/eris/tools.py b/eris/tools.py index 5dc2237..546fa61 100755 --- a/eris/tools.py +++ b/eris/tools.py @@ -31,7 +31,7 @@ import toml import eris import fill3 import lscolors -import termstr.termstr as termstr +import termstr PYTHON_VERSION = "3.9" diff --git a/fill3.py b/fill3.py index 5774e8e..c5250c6 100755 --- a/fill3.py +++ b/fill3.py @@ -10,7 +10,7 @@ import signal import sys import termstr.terminal as terminal -import termstr.termstr as termstr +import termstr def appearance_is_valid(appearance): diff --git a/termstr/__init__.py b/termstr/__init__.py index e69de29..6fa9425 100644 --- a/termstr/__init__.py +++ b/termstr/__init__.py @@ -0,0 +1,399 @@ + + +import collections +import functools +import html +import itertools +import os +import weakref + +import pygments.formatters.terminal256 +import cwcwidth + +import termstr.ColorMap + + +ESC = "\x1b" + +NORMAL = "[m" +BOLD = "[1m" +ITALIC = "[3m" +UNDERLINE = "[4m" + + +xterm_colormap = termstr.ColorMap.XTermColorMap() + + +def color(color_number, is_foreground): + return f"[{'38' if is_foreground else '48'};5;{color_number:d}m" + + +def rgb_color(rgb, is_foreground): + return f"[{'38' if is_foreground else '48'};2;" + "%i;%i;%im" % rgb + + +@functools.lru_cache() +def xterm_color_to_rgb(color_index): + return termstr.ColorMap._rgb(xterm_colormap.colors[color_index]) + + +class Color: + + # https://en.wikipedia.org/wiki/Natural_Color_System + black = (0, 0, 0) + white = (255, 255, 255) + red = (196, 2, 51) + green = (0, 159, 107) + dark_green = (0, 119, 80) + blue = (0, 135, 189) + lime = (0, 255, 0) + yellow = (255, 211, 0) + grey_30 = (30, 30, 30) + grey_50 = (50, 50, 50) + grey_80 = (80, 80, 80) + grey_100 = (100, 100, 100) + grey_150 = (150, 150, 150) + grey_200 = (200, 200, 200) + light_blue = (173, 216, 230) + purple = (200, 0, 200) + brown = (150, 75, 0) + orange = (255, 153, 0) + + +class CharStyle: + + _POOL = weakref.WeakValueDictionary() + _TERMINAL256_FORMATTER = \ + pygments.formatters.terminal256.Terminal256Formatter() + + def __new__(cls, fg_color=None, bg_color=None, is_bold=False, + is_italic=False, is_underlined=False): + if fg_color is None: + fg_color = Color.white + if bg_color is None: + bg_color = Color.black + key = (fg_color, bg_color, is_bold, is_italic, is_underlined) + try: + return CharStyle._POOL[key] + except KeyError: + obj = object.__new__(cls) + obj.fg_color, obj.bg_color, obj.is_bold, obj.is_italic, \ + obj.is_underlined = key + return CharStyle._POOL.setdefault(key, obj) + + def __getnewargs__(self): + return (self.fg_color, self.bg_color, self.is_bold, self.is_italic, + self.is_underlined) + + def __getstate__(self): + state = self.__dict__.copy() + if "_cache" in state: + del state["_cache"] + return state + + def __setstate__(self, state): + self.__dict__ = state + + def __repr__(self): + attributes = [] + if self.is_bold: + attributes.append("b") + if self.is_italic: + attributes.append("i") + if self.is_underlined: + attributes.append("u") + return (f"") + + def _color_code(self, color_, is_foreground): + if isinstance(color_, int): + return color(color_, is_foreground) + else: # true color + if os.environ.get("TERM", None) == "xterm": + closest_color = self._TERMINAL256_FORMATTER._closest_color( + *color_) + return color(closest_color, is_foreground) + else: + return rgb_color(color_, is_foreground) + + @functools.cached_property + def code_for_term(self): + fg_termcode = ESC + self._color_code(self.fg_color, True) + bg_termcode = ESC + self._color_code(self.bg_color, False) + bold_code = (ESC + BOLD) if self.is_bold else "" + italic_code = ((ESC + ITALIC) if self.is_italic else "") + underline_code = ((ESC + UNDERLINE) if self.is_underlined else "") + return "".join([ESC, NORMAL, fg_termcode, bg_termcode, bold_code, + italic_code, underline_code]) + + def as_html(self): + bold_code = "font-weight:bold; " if self.is_bold else "" + italic_code = "font-style:italic; " if self.is_italic else "" + underline_code = ("text-decoration:underline; " + if self.is_underlined else "") + fg_color = (self.fg_color if type(self.fg_color) == tuple + else xterm_color_to_rgb(self.fg_color)) + bg_color = (self.bg_color if type(self.bg_color) == tuple + else xterm_color_to_rgb(self.bg_color)) + return (f"") + + +def _join_lists(lists): + return list(itertools.chain.from_iterable(lists)) + + +_ZERO_WIDTH_SPACE = "\u200b" + + +def _pad_wide_chars(str_): + return "".join(f"{char}{_ZERO_WIDTH_SPACE}" + if cwcwidth.wcwidth(char) == 2 else char for char in str_) + + +class TermStr(collections.UserString): + + def __init__(self, data, style=CharStyle()): + if isinstance(style, tuple): + self.data = data + self.style = style + else: + try: + self.data, self.style = data.data, data.style + except AttributeError: + self.data = _pad_wide_chars(data) + self.style = (style,) * len(self.data) + + @classmethod + def from_term(cls, data): + data = data.expandtabs(tabsize=4) + parts = data.split(ESC) + fg_color, bg_color = None, None + is_bold, is_italic, is_underlined = False, False, False + result_parts = [parts[0]] + for part in parts[1:]: + if part.startswith("[K"): + end_index = part.index("K") + codes = [] + else: + try: + end_index = part.index("m") + except ValueError: + continue + codes = part[1:end_index].split(";") + previous_code = None + for index, code in enumerate(codes): + try: + code_int = int(code) + except ValueError: + code_int = None + if code in ["", "0", "00"]: # Normal + is_bold, is_italic, is_underlined = False, False, False + fg_color, bg_color = None, None + elif code in ["01", "1"]: # bold + is_bold = True + elif code in ["03", "3"]: # italic + is_italic = True + elif code in ["04", "4"]: # underline + is_underlined = True + elif code_int and 30 <= code_int <= 37 : # dim fg color + fg_color = int(code[1]) + elif code_int and 40 <= code_int <= 47: # dim bg color + bg_color = int(code[1]) + elif code_int and 90 <= code_int <= 97: # high fg color + fg_color = int(code[1]) + 8 + elif code_int and 100 <= code_int <= 107: # high bg color + bg_color = int(code[2]) + 8 + elif code == "5" and previous_code == "38": # simple fg color + fg_color = int(codes[index+1]) + codes[index+1:index+2] = [] + elif code == "5" and previous_code == "48": # simple bg color + bg_color = int(codes[index+1]) + codes[index+1:index+2] = [] + elif code == "2" and previous_code == "38": # rgb fg color + fg_color = tuple(int(component) + for component in codes[index+1:index+4]) + codes[index+1:index+4] = [] + elif code == "2" and previous_code == "48": # rgb bg color + bg_color = tuple(int(component) + for component in codes[index+1:index+4]) + codes[index+1:index+4] = [] + previous_code = code + result_parts.append(cls(part[end_index+1:], + CharStyle(fg_color, bg_color, is_bold, + is_italic, is_underlined))) + return cls("").join(result_parts) + + def __eq__(self, other): + return (self is other or + (isinstance(other, self.__class__) and + self.data == other.data and self.style == other.style)) + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash((self.data, self.style)) + + @functools.cached_property + def _partition_style(self): + if self.data == "": + return [] + last_style, last_index = None, 0 + result = [] + for index, style in enumerate(self.style): + if style != last_style: + if last_style is not None: + result.append((last_style, last_index, index)) + last_style, last_index = style, index + result.append((last_style, last_index, len(self.style))) + return result + + def __str__(self): + return "".join(_join_lists( + [style.code_for_term, self.data[start_index:end_index]] + for style, start_index, end_index in self._partition_style) + + [ESC + NORMAL]) + + def __repr__(self): + return f"" + + def __add__(self, other): + if isinstance(other, str): + other = TermStr(other) + return self.__class__(self.data + other.data, self.style + other.style) + + def __radd__(self, other): + if isinstance(other, str): + other = TermStr(other) + return self.__class__(other.data + self.data, other.style + self.style) + + def __mul__(self, n): + return self.__class__(self.data*n, self.style*n) + __rmul__ = __mul__ + + def __getitem__(self, index): + data = self.data[index] + if len(data) == 0: + result = "" + else: + first_char = " " if data[0] == _ZERO_WIDTH_SPACE else data[0] + if len(data) == 1: + result = first_char + else: + end_char = " " if cwcwidth.wcwidth(data[-1]) == 2 else data[-1] + result = first_char + data[1:-1] + end_char + return self.__class__(result, self.style[index]) + + def join(self, parts): + parts = [TermStr(part) if isinstance(part, str) else part + for part in parts] + joined_style = _join_lists(self.style + part.style for part in parts) + return self.__class__(self.data.join(part.data for part in parts), + tuple(joined_style[len(self.style):])) + + def _split_style(self, parts, sep_length): + result = [] + cursor = 0 + for part in parts: + style_part = self.style[cursor:cursor+len(part)] + result.append(self.__class__(part, style_part)) + cursor += (len(part) + sep_length) + return result + + def split(self, sep=None, maxsplit=-1): + return self._split_style(self.data.split(sep, maxsplit), len(sep)) + + def splitlines(self, keepends=0): + result = [] + cursor = 0 + for line in self.data.splitlines(keepends=True): + result_line = line if keepends else line.rstrip("\r\n") + style_part = self.style[cursor:cursor+len(result_line)] + result.append(self.__class__(result_line, style_part)) + cursor += len(line) + return result + + def capitalize(self): + return self.__class__(self.data.capitalize(), self.style) + + def lower(self): + return self.__class__(self.data.lower(), self.style) + + def swapcase(self): + return self.__class__(self.data.swapcase(), self.style) + + def title(self): + return self.__class__(self.data.title(), self.style) + + def upper(self): + return self.__class__(self.data.upper(), self.style) + + def ljust(self, width, fillchar=" "): + return self + self.__class__(fillchar * (width - len(self.data))) + + def rjust(self, width, fillchar=" "): + return self.__class__(fillchar * (width - len(self.data))) + self + + def center(self, width, fillchar=" "): + left_width = (width - len(self.data)) // 2 + if left_width < 1: + return self + return (self.__class__(fillchar * left_width) + self + + self.__class__(fillchar * + (width - left_width - len(self.data)))) + + # Below are extra methods useful for termstrs. + + def transform_style(self, transform_func): + new_style = tuple(_join_lists( + [transform_func(style)] * (end_index - start_index) + for style, start_index, end_index in self._partition_style)) + return self.__class__(self.data, new_style) + + def bold(self): + def make_bold(style): + return CharStyle(style.fg_color, style.bg_color, is_bold=True, + is_italic=style.is_italic, + is_underlined=style.is_underlined) + return self.transform_style(make_bold) + + def underline(self): + def make_underlined(style): + return CharStyle(style.fg_color, style.bg_color, + is_bold=style.is_bold, is_italic=style.is_italic, + is_underlined=True) + return self.transform_style(make_underlined) + + def italic(self): + def make_italic(style): + return CharStyle(style.fg_color, style.bg_color, + is_bold=style.is_bold, is_italic=True, + is_underlined=style.is_underlined) + return self.transform_style(make_italic) + + def fg_color(self, fg_color): + def set_fgcolor(style): + return CharStyle(fg_color, style.bg_color, is_bold=style.is_bold, + is_italic=style.is_italic, + is_underlined=style.is_underlined) + return self.transform_style(set_fgcolor) + + def bg_color(self, bg_color): + def set_bgcolor(style): + return CharStyle(style.fg_color, bg_color, is_bold=style.is_bold, + is_italic=style.is_italic, + is_underlined=style.is_underlined) + return self.transform_style(set_bgcolor) + + def as_html(self): + result = [] + styles = set() + for style, start_index, end_index in self._partition_style: + styles.add(style) + encoded = str(html.escape(self.data[start_index:end_index]).encode( + "ascii", "xmlcharrefreplace"))[2:-1] + encoded = encoded.replace("\\\\", "\\") + result.append(f'{encoded}') + return "".join(result), styles diff --git a/termstr/termstr.py b/termstr/termstr.py deleted file mode 100644 index 6fa9425..0000000 --- a/termstr/termstr.py +++ /dev/null @@ -1,399 +0,0 @@ - - -import collections -import functools -import html -import itertools -import os -import weakref - -import pygments.formatters.terminal256 -import cwcwidth - -import termstr.ColorMap - - -ESC = "\x1b" - -NORMAL = "[m" -BOLD = "[1m" -ITALIC = "[3m" -UNDERLINE = "[4m" - - -xterm_colormap = termstr.ColorMap.XTermColorMap() - - -def color(color_number, is_foreground): - return f"[{'38' if is_foreground else '48'};5;{color_number:d}m" - - -def rgb_color(rgb, is_foreground): - return f"[{'38' if is_foreground else '48'};2;" + "%i;%i;%im" % rgb - - -@functools.lru_cache() -def xterm_color_to_rgb(color_index): - return termstr.ColorMap._rgb(xterm_colormap.colors[color_index]) - - -class Color: - - # https://en.wikipedia.org/wiki/Natural_Color_System - black = (0, 0, 0) - white = (255, 255, 255) - red = (196, 2, 51) - green = (0, 159, 107) - dark_green = (0, 119, 80) - blue = (0, 135, 189) - lime = (0, 255, 0) - yellow = (255, 211, 0) - grey_30 = (30, 30, 30) - grey_50 = (50, 50, 50) - grey_80 = (80, 80, 80) - grey_100 = (100, 100, 100) - grey_150 = (150, 150, 150) - grey_200 = (200, 200, 200) - light_blue = (173, 216, 230) - purple = (200, 0, 200) - brown = (150, 75, 0) - orange = (255, 153, 0) - - -class CharStyle: - - _POOL = weakref.WeakValueDictionary() - _TERMINAL256_FORMATTER = \ - pygments.formatters.terminal256.Terminal256Formatter() - - def __new__(cls, fg_color=None, bg_color=None, is_bold=False, - is_italic=False, is_underlined=False): - if fg_color is None: - fg_color = Color.white - if bg_color is None: - bg_color = Color.black - key = (fg_color, bg_color, is_bold, is_italic, is_underlined) - try: - return CharStyle._POOL[key] - except KeyError: - obj = object.__new__(cls) - obj.fg_color, obj.bg_color, obj.is_bold, obj.is_italic, \ - obj.is_underlined = key - return CharStyle._POOL.setdefault(key, obj) - - def __getnewargs__(self): - return (self.fg_color, self.bg_color, self.is_bold, self.is_italic, - self.is_underlined) - - def __getstate__(self): - state = self.__dict__.copy() - if "_cache" in state: - del state["_cache"] - return state - - def __setstate__(self, state): - self.__dict__ = state - - def __repr__(self): - attributes = [] - if self.is_bold: - attributes.append("b") - if self.is_italic: - attributes.append("i") - if self.is_underlined: - attributes.append("u") - return (f"") - - def _color_code(self, color_, is_foreground): - if isinstance(color_, int): - return color(color_, is_foreground) - else: # true color - if os.environ.get("TERM", None) == "xterm": - closest_color = self._TERMINAL256_FORMATTER._closest_color( - *color_) - return color(closest_color, is_foreground) - else: - return rgb_color(color_, is_foreground) - - @functools.cached_property - def code_for_term(self): - fg_termcode = ESC + self._color_code(self.fg_color, True) - bg_termcode = ESC + self._color_code(self.bg_color, False) - bold_code = (ESC + BOLD) if self.is_bold else "" - italic_code = ((ESC + ITALIC) if self.is_italic else "") - underline_code = ((ESC + UNDERLINE) if self.is_underlined else "") - return "".join([ESC, NORMAL, fg_termcode, bg_termcode, bold_code, - italic_code, underline_code]) - - def as_html(self): - bold_code = "font-weight:bold; " if self.is_bold else "" - italic_code = "font-style:italic; " if self.is_italic else "" - underline_code = ("text-decoration:underline; " - if self.is_underlined else "") - fg_color = (self.fg_color if type(self.fg_color) == tuple - else xterm_color_to_rgb(self.fg_color)) - bg_color = (self.bg_color if type(self.bg_color) == tuple - else xterm_color_to_rgb(self.bg_color)) - return (f"") - - -def _join_lists(lists): - return list(itertools.chain.from_iterable(lists)) - - -_ZERO_WIDTH_SPACE = "\u200b" - - -def _pad_wide_chars(str_): - return "".join(f"{char}{_ZERO_WIDTH_SPACE}" - if cwcwidth.wcwidth(char) == 2 else char for char in str_) - - -class TermStr(collections.UserString): - - def __init__(self, data, style=CharStyle()): - if isinstance(style, tuple): - self.data = data - self.style = style - else: - try: - self.data, self.style = data.data, data.style - except AttributeError: - self.data = _pad_wide_chars(data) - self.style = (style,) * len(self.data) - - @classmethod - def from_term(cls, data): - data = data.expandtabs(tabsize=4) - parts = data.split(ESC) - fg_color, bg_color = None, None - is_bold, is_italic, is_underlined = False, False, False - result_parts = [parts[0]] - for part in parts[1:]: - if part.startswith("[K"): - end_index = part.index("K") - codes = [] - else: - try: - end_index = part.index("m") - except ValueError: - continue - codes = part[1:end_index].split(";") - previous_code = None - for index, code in enumerate(codes): - try: - code_int = int(code) - except ValueError: - code_int = None - if code in ["", "0", "00"]: # Normal - is_bold, is_italic, is_underlined = False, False, False - fg_color, bg_color = None, None - elif code in ["01", "1"]: # bold - is_bold = True - elif code in ["03", "3"]: # italic - is_italic = True - elif code in ["04", "4"]: # underline - is_underlined = True - elif code_int and 30 <= code_int <= 37 : # dim fg color - fg_color = int(code[1]) - elif code_int and 40 <= code_int <= 47: # dim bg color - bg_color = int(code[1]) - elif code_int and 90 <= code_int <= 97: # high fg color - fg_color = int(code[1]) + 8 - elif code_int and 100 <= code_int <= 107: # high bg color - bg_color = int(code[2]) + 8 - elif code == "5" and previous_code == "38": # simple fg color - fg_color = int(codes[index+1]) - codes[index+1:index+2] = [] - elif code == "5" and previous_code == "48": # simple bg color - bg_color = int(codes[index+1]) - codes[index+1:index+2] = [] - elif code == "2" and previous_code == "38": # rgb fg color - fg_color = tuple(int(component) - for component in codes[index+1:index+4]) - codes[index+1:index+4] = [] - elif code == "2" and previous_code == "48": # rgb bg color - bg_color = tuple(int(component) - for component in codes[index+1:index+4]) - codes[index+1:index+4] = [] - previous_code = code - result_parts.append(cls(part[end_index+1:], - CharStyle(fg_color, bg_color, is_bold, - is_italic, is_underlined))) - return cls("").join(result_parts) - - def __eq__(self, other): - return (self is other or - (isinstance(other, self.__class__) and - self.data == other.data and self.style == other.style)) - - def __ne__(self, other): - return not self.__eq__(other) - - def __hash__(self): - return hash((self.data, self.style)) - - @functools.cached_property - def _partition_style(self): - if self.data == "": - return [] - last_style, last_index = None, 0 - result = [] - for index, style in enumerate(self.style): - if style != last_style: - if last_style is not None: - result.append((last_style, last_index, index)) - last_style, last_index = style, index - result.append((last_style, last_index, len(self.style))) - return result - - def __str__(self): - return "".join(_join_lists( - [style.code_for_term, self.data[start_index:end_index]] - for style, start_index, end_index in self._partition_style) + - [ESC + NORMAL]) - - def __repr__(self): - return f"" - - def __add__(self, other): - if isinstance(other, str): - other = TermStr(other) - return self.__class__(self.data + other.data, self.style + other.style) - - def __radd__(self, other): - if isinstance(other, str): - other = TermStr(other) - return self.__class__(other.data + self.data, other.style + self.style) - - def __mul__(self, n): - return self.__class__(self.data*n, self.style*n) - __rmul__ = __mul__ - - def __getitem__(self, index): - data = self.data[index] - if len(data) == 0: - result = "" - else: - first_char = " " if data[0] == _ZERO_WIDTH_SPACE else data[0] - if len(data) == 1: - result = first_char - else: - end_char = " " if cwcwidth.wcwidth(data[-1]) == 2 else data[-1] - result = first_char + data[1:-1] + end_char - return self.__class__(result, self.style[index]) - - def join(self, parts): - parts = [TermStr(part) if isinstance(part, str) else part - for part in parts] - joined_style = _join_lists(self.style + part.style for part in parts) - return self.__class__(self.data.join(part.data for part in parts), - tuple(joined_style[len(self.style):])) - - def _split_style(self, parts, sep_length): - result = [] - cursor = 0 - for part in parts: - style_part = self.style[cursor:cursor+len(part)] - result.append(self.__class__(part, style_part)) - cursor += (len(part) + sep_length) - return result - - def split(self, sep=None, maxsplit=-1): - return self._split_style(self.data.split(sep, maxsplit), len(sep)) - - def splitlines(self, keepends=0): - result = [] - cursor = 0 - for line in self.data.splitlines(keepends=True): - result_line = line if keepends else line.rstrip("\r\n") - style_part = self.style[cursor:cursor+len(result_line)] - result.append(self.__class__(result_line, style_part)) - cursor += len(line) - return result - - def capitalize(self): - return self.__class__(self.data.capitalize(), self.style) - - def lower(self): - return self.__class__(self.data.lower(), self.style) - - def swapcase(self): - return self.__class__(self.data.swapcase(), self.style) - - def title(self): - return self.__class__(self.data.title(), self.style) - - def upper(self): - return self.__class__(self.data.upper(), self.style) - - def ljust(self, width, fillchar=" "): - return self + self.__class__(fillchar * (width - len(self.data))) - - def rjust(self, width, fillchar=" "): - return self.__class__(fillchar * (width - len(self.data))) + self - - def center(self, width, fillchar=" "): - left_width = (width - len(self.data)) // 2 - if left_width < 1: - return self - return (self.__class__(fillchar * left_width) + self + - self.__class__(fillchar * - (width - left_width - len(self.data)))) - - # Below are extra methods useful for termstrs. - - def transform_style(self, transform_func): - new_style = tuple(_join_lists( - [transform_func(style)] * (end_index - start_index) - for style, start_index, end_index in self._partition_style)) - return self.__class__(self.data, new_style) - - def bold(self): - def make_bold(style): - return CharStyle(style.fg_color, style.bg_color, is_bold=True, - is_italic=style.is_italic, - is_underlined=style.is_underlined) - return self.transform_style(make_bold) - - def underline(self): - def make_underlined(style): - return CharStyle(style.fg_color, style.bg_color, - is_bold=style.is_bold, is_italic=style.is_italic, - is_underlined=True) - return self.transform_style(make_underlined) - - def italic(self): - def make_italic(style): - return CharStyle(style.fg_color, style.bg_color, - is_bold=style.is_bold, is_italic=True, - is_underlined=style.is_underlined) - return self.transform_style(make_italic) - - def fg_color(self, fg_color): - def set_fgcolor(style): - return CharStyle(fg_color, style.bg_color, is_bold=style.is_bold, - is_italic=style.is_italic, - is_underlined=style.is_underlined) - return self.transform_style(set_fgcolor) - - def bg_color(self, bg_color): - def set_bgcolor(style): - return CharStyle(style.fg_color, bg_color, is_bold=style.is_bold, - is_italic=style.is_italic, - is_underlined=style.is_underlined) - return self.transform_style(set_bgcolor) - - def as_html(self): - result = [] - styles = set() - for style, start_index, end_index in self._partition_style: - styles.add(style) - encoded = str(html.escape(self.data[start_index:end_index]).encode( - "ascii", "xmlcharrefreplace"))[2:-1] - encoded = encoded.replace("\\\\", "\\") - result.append(f'{encoded}') - return "".join(result), styles diff --git a/tests/termstr_test.py b/tests/termstr_test.py index b7d9bd6..9b309e1 100755 --- a/tests/termstr_test.py +++ b/tests/termstr_test.py @@ -8,8 +8,8 @@ import unittest os.environ["TERM"] = "xterm-256color" import termstr.terminal as terminal -from termstr.termstr import TermStr, CharStyle -import termstr.termstr as termstr +from termstr import TermStr, CharStyle +import termstr class CharStyleTests(unittest.TestCase):