Move {- and %c color markup to contrib, make easily extendable, as per #1229

This commit is contained in:
Griatch 2017-07-19 20:32:21 +02:00
parent 83163ae4b3
commit 03f4ecb3a4
4 changed files with 168 additions and 98 deletions

View file

@ -6,9 +6,6 @@ to apply colour to text according to the ANSI standard.
Examples:
This is |rRed text|n and this is normal again.
This is {rRed text{n and this is normal again. # soon to be depreciated
This is %crRed text%cn and this is normal again. # depreciated
Mostly you should not need to call parse_ansi() explicitly;
it is run by Evennia just before returning data to/from the
@ -19,13 +16,17 @@ the ansi mapping.
from builtins import object, range
import re
from collections import OrderedDict
from django.conf import settings
from evennia.utils import utils
from evennia.utils import logger
from evennia.utils.utils import to_str, to_unicode
from future.utils import with_metaclass
# ANSI definitions
ANSI_BEEP = "\07"
@ -70,10 +71,11 @@ ANSI_SPACE = " "
# Escapes
ANSI_ESCAPES = ("{{", "\\\\", "\|\|")
from collections import OrderedDict
_PARSE_CACHE = OrderedDict()
_PARSE_CACHE_SIZE = 10000
_COLOR_NO_DEFAULT = settings.COLOR_NO_DEFAULT
class ANSIParser(object):
"""
@ -97,7 +99,7 @@ class ANSIParser(object):
processed (str): The processed match string.
"""
return self.ansi_map.get(ansimatch.group(), "")
return self.ansi_map_dict.get(ansimatch.group(), "")
def sub_brightbg(self, ansimatch):
"""
@ -113,7 +115,7 @@ class ANSIParser(object):
"""
return self.ansi_bright_bgs_map.get(ansimatch.group(), "")
def sub_xterm256(self, rgbmatch, use_xterm256=False):
def sub_xterm256(self, rgbmatch, use_xterm256=False, color_type="fg"):
"""
This is a replacer method called by `re.sub` with the matched
tag. It must return the correct ansi sequence.
@ -124,6 +126,7 @@ class ANSIParser(object):
Args:
rgbmatch (re.matchobject): The match.
use_xterm256 (bool, optional): Don't convert 256-colors to 16.
color_type (str): One of 'fg', 'bg', 'gfg', 'gbg'.
Returns:
processed (str): The processed match string.
@ -133,19 +136,26 @@ class ANSIParser(object):
return ""
# get tag, stripping the initial marker
rgbtag = rgbmatch.group()[1:]
#rgbtag = rgbmatch.group()[1:]
background = color_type in ("bg", "gbg")
grayscale = color_type in ("gfg", "gbg")
background = rgbtag[0] == '['
grayscale = rgbtag[0 + int(background)] == '='
if not grayscale:
# 6x6x6 color-cube (xterm indexes 16-231)
if background:
red, green, blue = int(rgbtag[1]), int(rgbtag[2]), int(rgbtag[3])
else:
red, green, blue = int(rgbtag[0]), int(rgbtag[1]), int(rgbtag[2])
try:
red, green, blue = [int(val) for val in rgbmatch.groups() if val is not None]
except (IndexError, ValueError):
logger.log_trace()
return rgbmatch.group(0)
else:
# grayscale values (xterm indexes 0, 232-255, 15) for full spectrum
letter = rgbtag[int(background) + 1]
try:
letter = [val for val in rgbmatch.groups() if val is not None][0]
except IndexError:
logger.log_trace()
return rgbmatch.group(0)
if letter == 'a':
colval = 16 # pure black @ index 16 (first color cube entry)
elif letter == 'z':
@ -286,8 +296,17 @@ class ANSIParser(object):
# pre-convert bright colors to xterm256 color tags
string = self.brightbg_sub.sub(self.sub_brightbg, string)
def do_xterm256(part):
return self.sub_xterm256(part, xterm256)
def do_xterm256_fg(part):
return self.sub_xterm256(part, xterm256, "fg")
def do_xterm256_bg(part):
return self.sub_xterm256(part, xterm256, "bg")
def do_xterm256_gfg(part):
return self.sub_xterm256(part, xterm256, "gfg")
def do_xterm256_gbg(part):
return self.sub_xterm256(part, xterm256, "gbg")
in_string = utils.to_str(string)
@ -295,7 +314,10 @@ class ANSIParser(object):
parsed_string = ""
parts = self.ansi_escapes.split(in_string) + [" "]
for part, sep in zip(parts[::2], parts[1::2]):
pstring = self.xterm256_sub.sub(do_xterm256, part)
pstring = self.xterm256_fg_sub.sub(do_xterm256_fg, part)
pstring = self.xterm256_bg_sub.sub(do_xterm256_bg, pstring)
pstring = self.xterm256_gfg_sub.sub(do_xterm256_gfg, pstring)
pstring = self.xterm256_gbg_sub.sub(do_xterm256_gbg, pstring)
pstring = self.ansi_sub.sub(self.sub_ansi, pstring)
parsed_string += "%s%s" % (pstring, sep[0].strip())
@ -319,55 +341,7 @@ class ANSIParser(object):
hilite = ANSI_HILITE
unhilite = ANSI_UNHILITE
ext_ansi_map = [
(r'{n', ANSI_NORMAL), # reset
(r'{/', ANSI_RETURN), # line break
(r'{-', ANSI_TAB), # tab
(r'{_', ANSI_SPACE), # space
(r'{*', ANSI_INVERSE), # invert
(r'{^', ANSI_BLINK), # blinking text (very annoying and not supported by all clients)
(r'{u', ANSI_UNDERLINE), # underline
(r'{r', hilite + ANSI_RED),
(r'{g', hilite + ANSI_GREEN),
(r'{y', hilite + ANSI_YELLOW),
(r'{b', hilite + ANSI_BLUE),
(r'{m', hilite + ANSI_MAGENTA),
(r'{c', hilite + ANSI_CYAN),
(r'{w', hilite + ANSI_WHITE), # pure white
(r'{x', hilite + ANSI_BLACK), # dark grey
(r'{R', unhilite + ANSI_RED),
(r'{G', unhilite + ANSI_GREEN),
(r'{Y', unhilite + ANSI_YELLOW),
(r'{B', unhilite + ANSI_BLUE),
(r'{M', unhilite + ANSI_MAGENTA),
(r'{C', unhilite + ANSI_CYAN),
(r'{W', unhilite + ANSI_WHITE), # light grey
(r'{X', unhilite + ANSI_BLACK), # pure black
# hilight-able colors
(r'{h', hilite),
(r'{H', unhilite),
(r'{!R', ANSI_RED),
(r'{!G', ANSI_GREEN),
(r'{!Y', ANSI_YELLOW),
(r'{!B', ANSI_BLUE),
(r'{!M', ANSI_MAGENTA),
(r'{!C', ANSI_CYAN),
(r'{!W', ANSI_WHITE), # light grey
(r'{!X', ANSI_BLACK), # pure black
# normal ANSI backgrounds
(r'{[R', ANSI_BACK_RED),
(r'{[G', ANSI_BACK_GREEN),
(r'{[Y', ANSI_BACK_YELLOW),
(r'{[B', ANSI_BACK_BLUE),
(r'{[M', ANSI_BACK_MAGENTA),
(r'{[C', ANSI_BACK_CYAN),
(r'{[W', ANSI_BACK_WHITE), # light grey background
(r'{[X', ANSI_BACK_BLACK), # pure black background
ansi_map = [
# alternative |-format
@ -420,21 +394,12 @@ class ANSIParser(object):
(r'|[W', ANSI_BACK_WHITE), # light grey background
(r'|[X', ANSI_BACK_BLACK) # pure black background
]
ext_ansi_map += settings.COLOR_ANSI_EXTRA_MAP
ansi_bright_bgs = [
# "bright" ANSI backgrounds using xterm256 since ANSI
# standard does not support it (will
# fallback to dark ANSI background colors if xterm256
# is not supported by client)
(r'{[r', r'{[500'),
(r'{[g', r'{[050'),
(r'{[y', r'{[550'),
(r'{[b', r'{[005'),
(r'{[m', r'{[505'),
(r'{[c', r'{[055'),
(r'{[w', r'{[555'), # white background
(r'{[x', r'{[222'), # dark grey background
# |-style variations
(r'|[r', r'|[500'),
@ -446,33 +411,43 @@ class ANSIParser(object):
(r'|[w', r'|[555'), # white background
(r'|[x', r'|[222')] # dark grey background
# xterm256 {123, %c134. These are replaced directly by
# xterm256. These are replaced directly by
# the sub_xterm256 method
xterm256_map = [
(r'\{[0-5]{3}', ""), # {123 - foreground colour
(r'\{\[[0-5]{3}', ""), # {[123 - background colour
# |-style
(r'\|[0-5]{3}', ""), # |123 - foreground colour
(r'\|\[[0-5]{3}', ""), # |[123 - background colour
# grayscale entries including ansi extremes: {=a .. {=z
(r'\{=[a-z]', ""),
(r'\{\[=[a-z]', ""),
(r'\|=[a-z]', ""),
(r'\|\[=[a-z]', ""),
]
if settings.COLOR_NO_DEFAULT:
ansi_map = settings.COLOR_ANSI_EXTRA_MAP
xterm256_fg = settings.COLOR_XTERM256_EXTRA_FG
xterm256_bg = settings.COLOR_XTERM256_EXTRA_BG
xterm256_gfg = settings.COLOR_XTERM256_EXTRA_GFG
xterm256_gbg = settings.COLOR_XTERM256_EXTRA_GBG
ansi_bright_bgs = settings.COLOR_ANSI_BRIGHT_BG_EXTRA_MAP
else:
xterm256_fg = [r'\|([0-5])([0-5])([0-5])'] # |123 - foreground colour
xterm256_bg = [r'\|\[([0-5])([0-5])([0-5])'] # |[123 - background colour
xterm256_gfg = [r'\|=([a-z])'] # |=a - greyscale foreground
xterm256_gbg = [r'\|\[=([a-z])'] # |[=a - greyscale background
ansi_map += settings.COLOR_ANSI_EXTRA_MAP
xterm256_fg += settings.COLOR_XTERM256_EXTRA_FG
xterm256_bg += settings.COLOR_XTERM256_EXTRA_BG
xterm256_gfg += settings.COLOR_XTERM256_EXTRA_GFG
xterm256_gbg += settings.COLOR_XTERM256_EXTRA_GBG
ansi_bright_bgs += settings.COLOR_ANSI_BRIGHT_BG_EXTRA_MAP
mxp_re = r'\|lc(.*?)\|lt(.*?)\|le'
# prepare regex matching
brightbg_sub = re.compile(r"|".join([r"(?<!\|)%s" % re.escape(tup[0]) for tup in ansi_bright_bgs]), re.DOTALL)
xterm256_sub = re.compile(r"|".join([tup[0] for tup in xterm256_map]), re.DOTALL)
ansi_sub = re.compile(r"|".join([re.escape(tup[0]) for tup in ext_ansi_map]), re.DOTALL)
xterm256_fg_sub = re.compile(r"|".join(xterm256_fg), re.DOTALL)
xterm256_bg_sub = re.compile(r"|".join(xterm256_bg), re.DOTALL)
xterm256_gfg_sub = re.compile(r"|".join(xterm256_gfg), re.DOTALL)
xterm256_gbg_sub = re.compile(r"|".join(xterm256_gbg), re.DOTALL)
# xterm256_sub = re.compile(r"|".join([tup[0] for tup in xterm256_map]), re.DOTALL)
ansi_sub = re.compile(r"|".join([re.escape(tup[0]) for tup in ansi_map]), re.DOTALL)
mxp_sub = re.compile(mxp_re, re.DOTALL)
# used by regex replacer to correctly map ansi sequences
ansi_map = dict(ext_ansi_map)
ansi_map_dict = dict(ansi_map)
ansi_bright_bgs_map = dict(ansi_bright_bgs)
# prepare matching ansi codes overall