Added m_len and made ANSIString ignore MXP.

This commit is contained in:
Jonathan Piacenti 2015-03-09 19:13:36 -05:00 committed by Griatch
parent b9e560660e
commit 571b1d5fad
3 changed files with 58 additions and 16 deletions

View file

@ -82,7 +82,7 @@ class ANSIParser(object):
"""
return self.ansi_map.get(ansimatch.group(), "")
def sub_xterm256(self, rgbmatch):
def sub_xterm256(self, rgbmatch, convert=False):
"""
This is a replacer method called by `re.sub` with the matched
tag. It must return the correct ansi sequence.
@ -102,7 +102,7 @@ class ANSIParser(object):
else:
red, green, blue = int(rgbtag[0]), int(rgbtag[1]), int(rgbtag[2])
if self.do_xterm256:
if convert:
colval = 16 + (red * 36) + (green * 6) + blue
#print "RGB colours:", red, green, blue
return "\033[%s8;5;%s%s%sm" % (3 + int(background), colval/100, (colval % 100)/10, colval%10)
@ -201,15 +201,16 @@ class ANSIParser(object):
if cachekey in _PARSE_CACHE:
return _PARSE_CACHE[cachekey]
self.do_xterm256 = xterm256
self.do_mxp = mxp
def do_xterm256(part):
return self.sub_xterm256(part, xterm256)
in_string = utils.to_str(string)
# do string replacement
parsed_string = ""
parts = self.ansi_escapes.split(in_string) + [" "]
for part, sep in zip(parts[::2], parts[1::2]):
pstring = self.xterm256_sub.sub(self.sub_xterm256, part)
pstring = self.xterm256_sub.sub(do_xterm256, part)
pstring = self.ansi_sub.sub(self.sub_ansi, pstring)
parsed_string += "%s%s" % (pstring, sep[0].strip())
@ -221,8 +222,7 @@ class ANSIParser(object):
# inserted in string)
return self.strip_raw_codes(parsed_string)
# cache and crop old cache
# cache and crop old cache
_PARSE_CACHE[cachekey] = parsed_string
if len(_PARSE_CACHE) > _PARSE_CACHE_SIZE:
_PARSE_CACHE.popitem(last=False)
@ -342,10 +342,6 @@ class ANSIParser(object):
ansi_re = r"\033\[[0-9;]+m"
ansi_regex = re.compile(ansi_re)
# merged regex for both ansi and mxp, for use by ansistring
mxp_tags = r'\{lc.*?\{lt|\{le'
tags_regex = re.compile("%s|%s" % (ansi_re, mxp_tags), re.DOTALL)
# escapes - these double-chars will be replaced with a single
# instance of each
ansi_escapes = re.compile(r"(%s)" % "|".join(ANSI_ESCAPES), re.DOTALL)
@ -527,7 +523,7 @@ class ANSIString(unicode):
decoded = True
if not decoded:
# Completely new ANSI String
clean_string = to_unicode(parser.parse_ansi(string, strip_ansi=True))
clean_string = to_unicode(parser.parse_ansi(string, strip_ansi=True, mxp=True))
string = parser.parse_ansi(string, xterm256=True, mxp=True)
elif clean_string is not None:
# We have an explicit clean string.
@ -781,8 +777,7 @@ class ANSIString(unicode):
"""
code_indexes = []
#for match in self.parser.ansi_regex.finditer(self._raw_string):
for match in self.parser.tags_regex.finditer(self._raw_string):
for match in self.parser.ansi_regex.finditer(self._raw_string):
code_indexes.extend(range(match.start(), match.end()))
if not code_indexes:
# Plain string, no ANSI codes.

View file

@ -5,8 +5,8 @@ try:
except ImportError:
from django.test import TestCase
from ansi import ANSIString
import utils
from .ansi import ANSIString
from evennia import utils
class ANSIStringTestCase(TestCase):
@ -121,6 +121,20 @@ class ANSIStringTestCase(TestCase):
result = u'\x1b[1m\x1b[32mTest\x1b[0m'
self.checker(target.capitalize(), result, u'Test')
def test_mxp_agnostic(self):
"""
Make sure MXP tags are not treated like ANSI codes, but normal text.
"""
mxp1 = "{lclook{ltat{le"
mxp2 = "Start to {lclook here{ltclick somewhere here{le first"
self.assertEqual(15, len(ANSIString(mxp1)))
self.assertEqual(53, len(ANSIString(mxp2)))
# These would indicate an issue with the tables.
self.assertEqual(len(ANSIString(mxp1)), len(ANSIString(mxp1).split("\n")[0]))
self.assertEqual(len(ANSIString(mxp2)), len(ANSIString(mxp2).split("\n")[0]))
self.assertEqual(mxp1, ANSIString(mxp1))
self.assertEqual(mxp2, unicode(ANSIString(mxp2)))
class TestIsIter(TestCase):
def test_is_iter(self):
@ -177,3 +191,26 @@ class TestListToString(TestCase):
self.assertEqual('1, 2 and 3', utils.list_to_string([1,2,3]))
self.assertEqual('"1", "2" and "3"', utils.list_to_string([1,2,3], endsep="and", addquote=True))
class TestMLen(TestCase):
"""
Verifies that m_len behaves like len in all situations except those
where MXP may be involved.
"""
def test_non_mxp_string(self):
self.assertEqual(utils.m_len('Test_string'), 11)
def test_mxp_string(self):
self.assertEqual(utils.m_len('{lclook{ltat{le'), 2)
def test_mxp_ansi_string(self):
self.assertEqual(utils.m_len(ANSIString('{lcl{gook{ltat{le{n')), 2)
def test_non_mxp_ansi_string(self):
self.assertEqual(utils.m_len(ANSIString('{gHello{n')), 5)
def test_list(self):
self.assertEqual(utils.m_len([None, None]), 2)
def test_dict(self):
self.assertEqual(utils.m_len({'hello': True, 'Goodbye': False}), 2)

View file

@ -1245,3 +1245,13 @@ def calledby(callerdepth=1):
return "[called by '%s': %s:%s %s]" % (frame[3], path, frame[2], frame[4])
def m_len(target):
"""
Provides length checking for strings with MXP patterns, and falls
back to normal len for other objects.
"""
# Would create circular import if in module root.
from evennia.utils.ansi import ANSI_PARSER
if inherits_from(target, basestring):
return len(ANSI_PARSER.strip_mxp(target))
return len(target)