Make utils.justify handle ANSIString properly. Resolve #2986

This commit is contained in:
Griatch 2022-11-27 18:29:25 +01:00
parent 20520d99d5
commit da03b22e1c
4 changed files with 52 additions and 12 deletions

View file

@ -118,7 +118,6 @@ from copy import copy, deepcopy
from textwrap import TextWrapper from textwrap import TextWrapper
from django.conf import settings from django.conf import settings
from evennia.utils.ansi import ANSIString from evennia.utils.ansi import ANSIString
from evennia.utils.utils import display_len as d_len from evennia.utils.utils import display_len as d_len
from evennia.utils.utils import is_iter, justify from evennia.utils.utils import is_iter, justify

View file

@ -330,3 +330,20 @@ class TestEvTable(EvenniaTestCase):
""" """
self._validate(expected, str(table)) 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))

View file

@ -11,12 +11,11 @@ from datetime import datetime, timedelta
import mock import mock
from django.test import TestCase from django.test import TestCase
from parameterized import parameterized
from twisted.internet import task
from evennia.utils import utils from evennia.utils import utils
from evennia.utils.ansi import ANSIString from evennia.utils.ansi import ANSIString
from evennia.utils.test_resources import BaseEvenniaTest from evennia.utils.test_resources import BaseEvenniaTest
from parameterized import parameterized
from twisted.internet import task
class TestIsIter(TestCase): class TestIsIter(TestCase):
@ -759,3 +758,17 @@ class TestJustify(TestCase):
def test_center_justify_small(self, width, expected): def test_center_justify_small(self, width, expected):
result = utils.justify("Task ID", width, align="c", indent=0, fillchar=" ") result = utils.justify("Task ID", width, align="c", indent=0, fillchar=" ")
self.assertEqual(expected, result) 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))

View file

@ -34,13 +34,12 @@ from django.core.validators import validate_email as django_validate_email
from django.utils import timezone from django.utils import timezone
from django.utils.html import strip_tags from django.utils.html import strip_tags
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from evennia.utils import logger
from simpleeval import simple_eval from simpleeval import simple_eval
from twisted.internet import reactor, threads from twisted.internet import reactor, threads
from twisted.internet.defer import returnValue # noqa - used as import target from twisted.internet.defer import returnValue # noqa - used as import target
from twisted.internet.task import deferLater from twisted.internet.task import deferLater
from evennia.utils import logger
_MULTIMATCH_TEMPLATE = settings.SEARCH_MULTIMATCH_TEMPLATE _MULTIMATCH_TEMPLATE = settings.SEARCH_MULTIMATCH_TEMPLATE
_EVENNIA_DIR = settings.EVENNIA_DIR _EVENNIA_DIR = settings.EVENNIA_DIR
_GAME_DIR = settings.GAME_DIR _GAME_DIR = settings.GAME_DIR
@ -51,6 +50,7 @@ ENCODINGS = settings.ENCODINGS
_TASK_HANDLER = None _TASK_HANDLER = None
_TICKER_HANDLER = None _TICKER_HANDLER = None
_STRIP_UNSAFE_TOKENS = None _STRIP_UNSAFE_TOKENS = None
_ANSISTRING = None
_GA = object.__getattribute__ _GA = object.__getattribute__
_SA = object.__setattr__ _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. 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): 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. distribute odd spaces to one of the gaps.
""" """
line_rest = width - (wlen + ngaps) line_rest = width - (wlen + ngaps)
gap = " " # minimum gap between words
gap = _ANSISTRING(" ") if is_ansi else " "
if line_rest > 0: if line_rest > 0:
if align == "l": if align == "l":
if line[-1] == "\n\n": if line[-1] == "\n\n":
@ -284,23 +293,25 @@ def justify(text, width=None, align="l", indent=0, fillchar=" "):
else: else:
line = crop(line, width=width, suffix="") line = crop(line, width=width, suffix="")
abs_lines.append(line) abs_lines.append(line)
return "\n".join(abs_lines) return lb.join(abs_lines)
# all other aligns requires splitting into paragraphs and words # all other aligns requires splitting into paragraphs and words
# split 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 = [] words = []
for ip, paragraph in enumerate(paragraphs): for ip, paragraph in enumerate(paragraphs):
if ip > 0: if ip > 0:
words.append(("\n", 0)) words.append(("\n", 0))
words.extend((word, len(word)) for word in paragraph.split()) words.extend((word, len(word)) for word in paragraph.split())
ngaps, wlen, line = 0, 0, []
if not words: if not words:
# Just whitespace! # Just whitespace!
return sp * width return sp * width
ngaps = 0
wlen = 0
line = []
lines = [] lines = []
while words: while words:
@ -328,8 +339,8 @@ def justify(text, width=None, align="l", indent=0, fillchar=" "):
if line: # catch any line left behind if line: # catch any line left behind
lines.append(_process_line(line)) lines.append(_process_line(line))
indentstring = sp * indent indentstring = sp * indent
out = "\n".join([indentstring + line for line in lines]) out = lb.join([indentstring + line for line in lines])
return "\n".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): def columnize(string, columns=2, spacing=4, align="l", width=None):