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

@ -716,10 +716,10 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
ap = ansi.ANSI_PARSER ap = ansi.ANSI_PARSER
# ansi colors # ansi colors
# show all ansi color-related codes # show all ansi color-related codes
col1 = ["%s%s|n" % (code, code.replace("|", "||")) for code, _ in ap.ext_ansi_map[48:56]] col1 = ["%s%s|n" % (code, code.replace("|", "||")) for code, _ in ap.ansi_map[48:56]]
col2 = ["%s%s|n" % (code, code.replace("|", "||")) for code, _ in ap.ext_ansi_map[56:64]] col2 = ["%s%s|n" % (code, code.replace("|", "||")) for code, _ in ap.ansi_map[56:64]]
col3 = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", "")) col3 = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", ""))
for code, _ in ap.ext_ansi_map[-8:]] for code, _ in ap.ansi_map[-8:]]
col4 = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", "")) col4 = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", ""))
for code, _ in ap.ansi_bright_bgs[-8:]] for code, _ in ap.ansi_bright_bgs[-8:]]
col2.extend(["" for _ in range(len(col1)-len(col2))]) col2.extend(["" for _ in range(len(col1)-len(col2))])

View file

@ -4,7 +4,9 @@ Testing suite for contrib folder
""" """
import sys
import datetime import datetime
from django.test import override_settings
from evennia.commands.default.tests import CommandTest from evennia.commands.default.tests import CommandTest
from evennia.utils.test_resources import EvenniaTest from evennia.utils.test_resources import EvenniaTest
from mock import Mock, patch from mock import Mock, patch
@ -984,3 +986,72 @@ class TestUnixCommand(CommandTest):
lines = ret.splitlines() lines = ret.splitlines()
self.assertTrue(any(l.startswith("usage:") for l in lines)) self.assertTrue(any(l.startswith("usage:") for l in lines))
self.assertTrue(any(l.startswith("dummy: error:") for l in lines)) self.assertTrue(any(l.startswith("dummy: error:") for l in lines))
from evennia.contrib import color_markups
from evennia.utils import ansi
class TestColorMarkup(EvenniaTest):
def test_default_markup(self):
reload(ansi)
self.assertEqual(ansi.parse_ansi("A |rred test"), 'A \x1b[1m\x1b[31mred test')
self.assertEqual(ansi.parse_ansi("A {rred test"), 'A {rred test')
self.assertEqual(ansi.parse_ansi("A %crred test"), "A %crred test")
@override_settings(
COLOR_ANSI_EXTRA_MAP=color_markups.CURLY_COLOR_ANSI_EXTRA_MAP,
COLOR_XTERM256_EXTRA_FG=color_markups.CURLY_COLOR_XTERM256_EXTRA_FG,
COLOR_XTERM256_EXTRA_BG=color_markups.CURLY_COLOR_XTERM256_EXTRA_BG,
COLOR_XTERM256_EXTRA_GFG=color_markups.CURLY_COLOR_XTERM256_EXTRA_GFG,
COLOR_XTERM256_EXTRA_GBG=color_markups.CURLY_COLOR_XTERM256_EXTRA_GBG,
COLOR_ANSI_BRIGHT_BG_EXTRA_MAP=color_markups.CURLY_COLOR_ANSI_BRIGHT_BG_EXTRA_MAP)
def test_curly_markup(self):
reload(ansi) # this is important since the module is initialized
self.assertEqual(ansi.parse_ansi("A |rred test"), 'A \x1b[1m\x1b[31mred test')
self.assertEqual(ansi.parse_ansi("A {rred test"), 'A \x1b[1m\x1b[31mred test')
self.assertEqual(ansi.parse_ansi("A {[Rred test"), 'A \x1b[41mred test')
self.assertEqual(ansi.parse_ansi("A |500red test"), 'A \x1b[1m\x1b[31mred test')
self.assertEqual(ansi.parse_ansi("A {500red test"), 'A \x1b[1m\x1b[31mred test')
self.assertEqual(ansi.parse_ansi("A {[500red test"), 'A \x1b[41mred test')
self.assertEqual(ansi.parse_ansi("A {=hgray test"), 'A \x1b[1m\x1b[30mgray test')
self.assertEqual(ansi.parse_ansi("A {[=hgray test"), 'A \x1b[40mgray test')
self.assertEqual(ansi.parse_ansi("A %crred test"), "A %crred test")
# fake ansi bright background
self.assertEqual(ansi.parse_ansi("A {[rred test"), 'A \x1b[41mred test')
@override_settings(
COLOR_ANSI_EXTRA_MAP=color_markups.MUX_COLOR_ANSI_EXTRA_MAP,
COLOR_XTERM256_EXTRA_FG=color_markups.MUX_COLOR_XTERM256_EXTRA_FG,
COLOR_XTERM256_EXTRA_BG=color_markups.MUX_COLOR_XTERM256_EXTRA_BG,
COLOR_XTERM256_EXTRA_GFG=color_markups.MUX_COLOR_XTERM256_EXTRA_GFG,
COLOR_XTERM256_EXTRA_GBG=color_markups.MUX_COLOR_XTERM256_EXTRA_GBG,
COLOR_ANSI_BRIGHT_BG_EXTRA_MAP=color_markups.MUX_COLOR_ANSI_BRIGHT_BG_EXTRA_MAP)
def test_mux_markup(self):
reload(ansi)
self.assertEqual(ansi.parse_ansi("A |rred test"), 'A \x1b[1m\x1b[31mred test')
self.assertEqual(ansi.parse_ansi("A %ch%crred test"), 'A \x1b[1m\x1b[31mred test')
self.assertEqual(ansi.parse_ansi("A %ch%crred test"), 'A \x1b[1m\x1b[31mred test')
self.assertEqual(ansi.parse_ansi("A |500red test"), 'A \x1b[1m\x1b[31mred test')
self.assertEqual(ansi.parse_ansi("A %c500red test"), 'A \x1b[1m\x1b[31mred test')
self.assertEqual(ansi.parse_ansi("A %c[500red test"), 'A \x1b[41mred test')
self.assertEqual(ansi.parse_ansi("A %c=hgray test"), 'A \x1b[1m\x1b[30mgray test')
self.assertEqual(ansi.parse_ansi("A %c[=hgray test"), 'A \x1b[40mgray test')
self.assertEqual(ansi.parse_ansi("A {rred test"), "A {rred test")
# fake ansi bright background
self.assertEqual(ansi.parse_ansi("A %ch%cRred test"), 'A \x1b[41mred test')
@override_settings(
COLOR_ANSI_EXTRA_MAP=color_markups.MUX_COLOR_ANSI_EXTRA_MAP,
COLOR_XTERM256_EXTRA_FG=color_markups.MUX_COLOR_XTERM256_EXTRA_FG,
COLOR_XTERM256_EXTRA_BG=color_markups.MUX_COLOR_XTERM256_EXTRA_BG,
COLOR_XTERM256_EXTRA_GFG=color_markups.MUX_COLOR_XTERM256_EXTRA_GFG,
COLOR_XTERM256_EXTRA_GBG=color_markups.MUX_COLOR_XTERM256_EXTRA_GBG,
COLOR_ANSI_BRIGHT_BG_EXTRA_MAP=color_markups.MUX_COLOR_ANSI_BRIGHT_BG_EXTRA_MAP,
COLOR_NO_DEFAULT=True)
def test_override(self):
reload(ansi)
self.assertEqual(ansi.parse_ansi("A |rred test"), "A |rred test")
self.assertEqual(ansi.parse_ansi("A {rred test"), "A {rred test")
self.assertEqual(ansi.parse_ansi("A %ch%crred test"), 'A \x1b[1m\x1b[31mred test')

View file

@ -331,10 +331,34 @@ PROTOTYPE_MODULES = ["world.prototypes"]
# dummyrunner for more information) # dummyrunner for more information)
DUMMYRUNNER_SETTINGS_MODULE = "evennia.server.profiling.dummyrunner_settings" DUMMYRUNNER_SETTINGS_MODULE = "evennia.server.profiling.dummyrunner_settings"
# Mapping to extend Evennia's normal ANSI color tags. The mapping is a list of # Mapping to extend Evennia's normal ANSI color tags. The mapping is a list of
# tuples mapping the tag to the ANSI convertion, like `("%c%r", ansi.ANSI_RED)` # tuples mapping the tag to the ANSI convertion, like `(r"%c%r", ansi.ANSI_RED)`
# (the evennia.utils.ansi module contains all ANSI escape sequences). This is # (the evennia.utils.ansi module contains all ANSI escape sequences). Default
# mainly supplied for support of legacy codebase tag formats. # is to use `|` and `|[` prefixes.
COLOR_ANSI_EXTRA_MAP = [] COLOR_ANSI_EXTRA_MAP = []
# Extend the available regexes for adding XTERM256 colors in-game. This is given
# as a list of regexes, where each regex must contain three anonymous groups for
# holding integers 0-5 for the red, green and blue components Default is
# is r'\|([0-5])([0-5])([0-5])', which allows e.g. |500 for red.
# XTERM256 foreground color replacement
COLOR_XTERM256_EXTRA_FG = []
# XTERM256 background color replacement. Default is \|\[([0-5])([0-5])([0-5])'
COLOR_XTERM256_EXTRA_BG = []
# Extend the available regexes for adding XTERM256 grayscale values in-game. Given
# as a list of regexes, where each regex must contain one anonymous group containing
# a single letter a-z to mark the level from white to black. Default is r'\|=([a-z])',
# which allows e.g. |=k for a medium gray.
# XTERM256 grayscale foreground
COLOR_XTERM256_EXTRA_GFG = []
# XTERM256 grayscale background. Default is \|\[=([a-z])'
COLOR_XTERM256_EXTRA_GBG = []
# ANSI does not support bright backgrounds, so Evennia fakes this by mapping it to
# XTERM256 backgrounds where supported. This is a list of tuples that maps the wanted
# regex to a valid XTERM256 background tag, such as `(r'{[r', r'{[500')`.
COLOR_ANSI_BRIGHT_BG_EXTRA_MAP = []
# If set True, the above color settings *replace* the default |-style color markdown
# rather than extend it.
COLOR_NO_DEFAULT = False
###################################################################### ######################################################################
# Default command sets # Default command sets

View file

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