From da03b22e1c5f3fa3110daf2089ca56eb63d18689 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 27 Nov 2022 18:29:25 +0100 Subject: [PATCH] Make utils.justify handle ANSIString properly. Resolve #2986 --- evennia/utils/evtable.py | 1 - evennia/utils/tests/test_evtable.py | 17 +++++++++++++++++ evennia/utils/tests/test_utils.py | 19 ++++++++++++++++--- evennia/utils/utils.py | 27 +++++++++++++++++++-------- 4 files changed, 52 insertions(+), 12 deletions(-) diff --git a/evennia/utils/evtable.py b/evennia/utils/evtable.py index 8517344f61..f6837a79a0 100644 --- a/evennia/utils/evtable.py +++ b/evennia/utils/evtable.py @@ -118,7 +118,6 @@ from copy import copy, deepcopy from textwrap import TextWrapper from django.conf import settings - from evennia.utils.ansi import ANSIString from evennia.utils.utils import display_len as d_len from evennia.utils.utils import is_iter, justify diff --git a/evennia/utils/tests/test_evtable.py b/evennia/utils/tests/test_evtable.py index 633f45af3b..116aaa0896 100644 --- a/evennia/utils/tests/test_evtable.py +++ b/evennia/utils/tests/test_evtable.py @@ -330,3 +330,20 @@ class TestEvTable(EvenniaTestCase): """ self._validate(expected, str(table)) + + def test_color_transfer(self): + """ + Testing https://github.com/evennia/evennia/issues/2986 + + EvTable swallowing color tags. + + """ + from evennia.utils.ansi import ANSI_CYAN, ANSI_RED + + row1 = "|cAn entire colored row|n" + row2 = "A single |rred|n word" + + table = evtable.EvTable(table=[[row1, row2]]) + + self.assertIn(ANSI_RED, str(table)) + self.assertIn(ANSI_CYAN, str(table)) diff --git a/evennia/utils/tests/test_utils.py b/evennia/utils/tests/test_utils.py index 755941960d..02ec4e3dc4 100644 --- a/evennia/utils/tests/test_utils.py +++ b/evennia/utils/tests/test_utils.py @@ -11,12 +11,11 @@ from datetime import datetime, timedelta import mock from django.test import TestCase -from parameterized import parameterized -from twisted.internet import task - from evennia.utils import utils from evennia.utils.ansi import ANSIString from evennia.utils.test_resources import BaseEvenniaTest +from parameterized import parameterized +from twisted.internet import task class TestIsIter(TestCase): @@ -759,3 +758,17 @@ class TestJustify(TestCase): def test_center_justify_small(self, width, expected): result = utils.justify("Task ID", width, align="c", indent=0, fillchar=" ") self.assertEqual(expected, result) + + def test_justify_ansi(self): + """ + Justify ansistring + + """ + + from evennia.utils.ansi import ANSI_RED + + line = ANSIString("This is a |rred|n word") + + result = utils.justify(line, align="c", width=30) + + self.assertIn(ANSI_RED, str(result)) diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index f3b4eba682..74a1daa41f 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -34,13 +34,12 @@ from django.core.validators import validate_email as django_validate_email from django.utils import timezone from django.utils.html import strip_tags from django.utils.translation import gettext as _ +from evennia.utils import logger from simpleeval import simple_eval from twisted.internet import reactor, threads from twisted.internet.defer import returnValue # noqa - used as import target from twisted.internet.task import deferLater -from evennia.utils import logger - _MULTIMATCH_TEMPLATE = settings.SEARCH_MULTIMATCH_TEMPLATE _EVENNIA_DIR = settings.EVENNIA_DIR _GAME_DIR = settings.GAME_DIR @@ -51,6 +50,7 @@ ENCODINGS = settings.ENCODINGS _TASK_HANDLER = None _TICKER_HANDLER = None _STRIP_UNSAFE_TOKENS = None +_ANSISTRING = None _GA = object.__getattribute__ _SA = object.__setattr__ @@ -236,6 +236,13 @@ def justify(text, width=None, align="l", indent=0, fillchar=" "): justified (str): The justified and indented block of text. """ + # we need to retain ansitrings + global _ANSISTRING + if not _ANSISTRING: + from evennia.utils.ansi import ANSIString as _ANSISTRING + + is_ansi = isinstance(text, _ANSISTRING) + lb = _ANSISTRING("\n") if is_ansi else "\n" def _process_line(line): """ @@ -244,7 +251,9 @@ def justify(text, width=None, align="l", indent=0, fillchar=" "): distribute odd spaces to one of the gaps. """ line_rest = width - (wlen + ngaps) - gap = " " # minimum gap between words + + gap = _ANSISTRING(" ") if is_ansi else " " + if line_rest > 0: if align == "l": if line[-1] == "\n\n": @@ -284,23 +293,25 @@ def justify(text, width=None, align="l", indent=0, fillchar=" "): else: line = crop(line, width=width, suffix="") abs_lines.append(line) - return "\n".join(abs_lines) + return lb.join(abs_lines) # all other aligns requires splitting into paragraphs and words # split into paragraphs and words - paragraphs = re.split("\n\s*?\n", text, re.MULTILINE) + paragraphs = [text] # re.split("\n\s*?\n", text, re.MULTILINE) words = [] for ip, paragraph in enumerate(paragraphs): if ip > 0: words.append(("\n", 0)) words.extend((word, len(word)) for word in paragraph.split()) - ngaps, wlen, line = 0, 0, [] if not words: # Just whitespace! return sp * width + ngaps = 0 + wlen = 0 + line = [] lines = [] while words: @@ -328,8 +339,8 @@ def justify(text, width=None, align="l", indent=0, fillchar=" "): if line: # catch any line left behind lines.append(_process_line(line)) indentstring = sp * indent - out = "\n".join([indentstring + line for line in lines]) - return "\n".join([indentstring + line for line in lines]) + out = lb.join([indentstring + line for line in lines]) + return lb.join([indentstring + line for line in lines]) def columnize(string, columns=2, spacing=4, align="l", width=None):