Implemented xterm256 colours. If not supported by client, converts to normal ANSI, as per kaViir's snippet. Integrates into Evennia's normal ansi parser and collaborates with TTYPE protocol to determine if the client supports it.

This commit is contained in:
Griatch 2011-11-20 14:56:07 +01:00
parent fb78758356
commit 4cd80284c9
6 changed files with 194 additions and 146 deletions

View file

@ -6,58 +6,42 @@ This is a IMC2 complacent version.
"""
import re
from src.utils.ansi import ANSIParser, ANSITable, parse_ansi
from src.utils import ansi
class IMCANSIParser(ANSIParser):
class IMCANSIParser(ansi.ANSIParser):
"""
This parser is per the IMC2 specification.
"""
def __init__(self):
normal = ansi.ANSI_NORMAL
hilite = ansi.ANSI_HILITE
self.ansi_map = [
# Random
(r'~Z', ANSITable.ansi["normal"]),
# Dark Grey
(r'~D', ANSITable.ansi["hilite"] + ANSITable.ansi["black"]),
(r'~z', ANSITable.ansi["hilite"] + ANSITable.ansi["black"]),
# Grey/reset
(r'~w', ANSITable.ansi["normal"]),
(r'~d', ANSITable.ansi["normal"]),
(r'~!', ANSITable.ansi["normal"]),
# Bold/hilite
(r'~L', ANSITable.ansi["hilite"]),
# White
(r'~W', ANSITable.ansi["normal"] + ANSITable.ansi["hilite"]),
# Dark Green
(r'~g', ANSITable.ansi["normal"] + ANSITable.ansi["green"]),
# Green
(r'~G', ANSITable.ansi["hilite"] + ANSITable.ansi["green"]),
# Dark magenta
(r'~p', ANSITable.ansi["normal"] + ANSITable.ansi["magenta"]),
(r'~m', ANSITable.ansi["normal"] + ANSITable.ansi["magenta"]),
# Magenta
(r'~M', ANSITable.ansi["hilite"] + ANSITable.ansi["magenta"]),
(r'~P', ANSITable.ansi["hilite"] + ANSITable.ansi["magenta"]),
# Black
(r'~x', ANSITable.ansi["normal"] + ANSITable.ansi["black"]),
# Cyan
(r'~c', ANSITable.ansi["normal"] + ANSITable.ansi["cyan"]),
# Dark Yellow (brown)
(r'~Y', ANSITable.ansi["hilite"] + ANSITable.ansi["yellow"]),
# Yellow
(r'~y', ANSITable.ansi["normal"] + ANSITable.ansi["yellow"]),
# Dark Blue
(r'~B', ANSITable.ansi["normal"] + ANSITable.ansi["blue"]),
# Blue
(r'~C', ANSITable.ansi["hilite"] + ANSITable.ansi["blue"]),
# Dark Red
(r'~r', ANSITable.ansi["normal"] + ANSITable.ansi["red"]),
# Red
(r'~R', ANSITable.ansi["normal"] + ANSITable.ansi["red"]),
# Dark Blue
(r'~b', ANSITable.ansi["normal"] + ANSITable.ansi["blue"]),
(r'~Z', normal), # Random
(r'~x', normal + ansi.ANSI_BLACK), # Black
(r'~D', hilite + ansi.ANSI_BLACK), # Dark Grey
(r'~z', hilite + ansi.ANSI_BLACK),
(r'~w', normal + ansi.ANSI_WHITE), # Grey
(r'~W', hilite + ansi.ANSI_WHITE), # White
(r'~g', normal + ansi.ANSI_GREEN), # Dark Green
(r'~G', hilite + ansi.ANSI_GREEN), # Green
(r'~p', normal + ansi.ANSI_MAGENTA), # Dark magenta
(r'~m', normal + ansi.ANSI_MAGENTA),
(r'~M', hilite + ansi.ANSI_MAGENTA), # Magenta
(r'~P', hilite + ansi.ANSI_MAGENTA),
(r'~c', normal + ansi.ANSI_CYAN), # Cyan
(r'~y', normal + ansi.ANSI_YELLOW), # Dark Yellow (brown)
(r'~Y', hilite + ansi.ANSI_YELLOW), # Yellow
(r'~b', normal + ansi.ANSI_BLUE), # Dark Blue
(r'~B', hilite + ansi.ANSI_BLUE), # Blue
(r'~C', hilite + ansi.ANSI_BLUE),
(r'~r', normal + ansi.ANSI_RED), # Dark Red
(r'~R', hilite + ansi.ANSI_RED), # Red
## Formatting
(r'\\r', ANSITable.ansi["normal"]),
(r'\\n', ANSITable.ansi["return"]),
(r'~L', hilite), # Bold/hilite
(r'~!', normal), # reset
(r'\\r', normal),
(r'\\n', ansi.ANSI_RETURN),
]
# prepare regex matching
self.ansi_sub = [(re.compile(sub[0], re.DOTALL), sub[1])

View file

@ -48,7 +48,6 @@ class Mssp(object):
"""
This is the normal operation.
"""
print "no mssp"
pass
def do_mssp(self, option):
@ -182,6 +181,3 @@ class Mssp(object):
# send to crawler by subnegotiation
self.protocol.requestNegotiation(MSSP, varlist)

View file

@ -36,7 +36,7 @@ class Session(object):
_attrs_to_sync = ['protocol_key', 'address', 'suid', 'sessid', 'uid', 'uname',
'logged_in', 'cid', 'encoding',
'conn_time', 'cmd_last', 'cmd_last_visible', 'cmd_total',
'protocol_flags', 'server_data']
'server_data']
def init_session(self, protocol_key, address, sessionhandler):
"""

View file

@ -36,9 +36,11 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
# negotiate mssp (crawler communication)
self.mssp = mssp.Mssp(self)
# add us to sessionhandler
self.sessionhandler.connect(self)
# add this new connection to sessionhandler so
# the Server becomes aware of it.
self.sessionhandler.connect(self)
def enableRemote(self, option):
"""
@ -137,8 +139,10 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
string = utils.to_str(string, encoding=self.encoding)
except Exception, e:
self.sendLine(str(e))
return
nomarkup = False
return
xterm256 = self.protocol_flags.get('TTYPE', {}).get('256 COLORS')
nomarkup = not (xterm256 or not self.protocol_flags.get('TTYPE')
or self.protocol_flags.get('TTYPE', {}).get('ANSI'))
raw = False
if type(data) == dict:
# check if we want escape codes to go through unparsed.
@ -147,5 +151,5 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
nomarkup = data.get("nomarkup", False)
if raw:
self.sendLine(string)
else:
self.sendLine(ansi.parse_ansi(string, strip_ansi=nomarkup))
else:
self.sendLine(ansi.parse_ansi(string, strip_ansi=nomarkup, xterm256=xterm256))

View file

@ -47,7 +47,7 @@ class Ttype(object):
self.protocol.negotiationMap[TTYPE] = self.do_ttype
# ask if client will ttype, connect callback if it does.
self.protocol.will(TTYPE).addCallbacks(self.do_ttype, self.no_ttype)
def no_ttype(self, option):
"""
Callback if ttype is not supported by client.
@ -79,7 +79,7 @@ class Ttype(object):
elif self.ttype_step == 3:
self.protocol.protocol_flags['TTYPE']['TERM'] = option
self.protocol.requestNegotiation(TTYPE, SEND)
elif self.ttype_step == 4 and option.startswith('MTTS'):
elif self.ttype_step == 4:
option = int(option.strip('MTTS '))
self.protocol.protocol_flags['TTYPE']['MTTS'] = option
for codenum, standard in MTTS:

View file

@ -16,51 +16,45 @@ user.
import re
from src.utils import utils
class ANSITable(object):
"""
A table defining the
standard ANSI command sequences.
# ANSI definitions
"""
ansi = {}
ansi["beep"] = "\07"
ansi["escape"] = "\033"
ansi["normal"] = "\033[0m"
ANSI_BEEP = "\07"
ANSI_ESCAPE = "\033"
ANSI_NORMAL = "\033[0m"
ansi["underline"] = "\033[4m"
ansi["hilite"] = "\033[1m"
ansi["blink"] = "\033[5m"
ansi["inverse"] = "\033[7m"
ansi["inv_hilite"] = "\033[1;7m"
ansi["inv_blink"] = "\033[7;5m"
ansi["blink_hilite"] = "\033[1;5m"
ansi["inv_blink_hilite"] = "\033[1;5;7m"
ANSI_UNDERLINE = "\033[4m"
ANSI_HILITE = "\033[1m"
ANSI_BLINK = "\033[5m"
ANSI_INVERSE = "\033[7m"
ANSI_INV_HILITE = "\033[1;7m"
ANSI_INV_BLINK = "\033[7;5m"
ANSI_BLINK_HILITE = "\033[1;5m"
ANSI_INV_BLINK_HILITE = "\033[1;5;7m"
# Foreground colors
ansi["black"] = "\033[30m"
ansi["red"] = "\033[31m"
ansi["green"] = "\033[32m"
ansi["yellow"] = "\033[33m"
ansi["blue"] = "\033[34m"
ansi["magenta"] = "\033[35m"
ansi["cyan"] = "\033[36m"
ansi["white"] = "\033[37m"
# Foreground colors
ANSI_BLACK = "\033[30m"
ANSI_RED = "\033[31m"
ANSI_GREEN = "\033[32m"
ANSI_YELLOW = "\033[33m"
ANSI_BLUE = "\033[34m"
ANSI_MAGENTA = "\033[35m"
ANSI_CYAN = "\033[36m"
ANSI_WHITE = "\033[37m"
# Background colors
ansi["back_black"] = "\033[40m"
ansi["back_red"] = "\033[41m"
ansi["back_green"] = "\033[42m"
ansi["back_yellow"] = "\033[43m"
ansi["back_blue"] = "\033[44m"
ansi["back_magenta"] = "\033[45m"
ansi["back_cyan"] = "\033[46m"
ansi["back_white"] = "\033[47m"
# Formatting Characters
ansi["return"] = "\r\n"
ansi["tab"] = "\t"
ansi["space"] = " "
# Background colors
ANSI_BACK_BLACK = "\033[40m"
ANSI_BACK_RED = "\033[41m"
ANSI_BACK_GREEN = "\033[42m"
ANSI_BACK_YELLOW = "\033[43m"
ANSI_BACK_BLUE = "\033[44m"
ANSI_BACK_MAGENTA = "\033[45m"
ANSI_BACK_CYAN = "\033[46m"
ANSI_BACK_WHITE = "\033[47m"
# Formatting Characters
ANSI_RETURN = "\r\n"
ANSI_TAB = "\t"
ANSI_SPACE = " "
class ANSIParser(object):
"""
@ -74,64 +68,132 @@ class ANSIParser(object):
# MUX-style mappings %cr %cn etc
self.mux_ansi_map = [
(r'%r', ANSITable.ansi["return"]),
(r'%t', ANSITable.ansi["tab"]),
(r'%b', ANSITable.ansi["space"]),
(r'%cf', ANSITable.ansi["blink"]),
(r'%ci', ANSITable.ansi["inverse"]),
(r'%ch', ANSITable.ansi["hilite"]),
(r'%cn', ANSITable.ansi["normal"]),
(r'%cx', ANSITable.ansi["black"]),
(r'%cX', ANSITable.ansi["back_black"]),
(r'%cr', ANSITable.ansi["red"]),
(r'%cR', ANSITable.ansi["back_red"]),
(r'%cg', ANSITable.ansi["green"]),
(r'%cG', ANSITable.ansi["back_green"]),
(r'%cy', ANSITable.ansi["yellow"]),
(r'%cY', ANSITable.ansi["back_yellow"]),
(r'%cb', ANSITable.ansi["blue"]),
(r'%cB', ANSITable.ansi["back_blue"]),
(r'%cm', ANSITable.ansi["magenta"]),
(r'%cM', ANSITable.ansi["back_magenta"]),
(r'%cc', ANSITable.ansi["cyan"]),
(r'%cC', ANSITable.ansi["back_cyan"]),
(r'%cw', ANSITable.ansi["white"]),
(r'%cW', ANSITable.ansi["back_white"]),
(r'%r', ANSI_RETURN),
(r'%t', ANSI_TAB),
(r'%b', ANSI_SPACE),
(r'%cf', ANSI_BLINK),
(r'%ci', ANSI_INVERSE),
(r'%ch', ANSI_HILITE),
(r'%cn', ANSI_NORMAL),
(r'%cx', ANSI_BLACK),
(r'%cX', ANSI_BACK_BLACK),
(r'%cr', ANSI_RED),
(r'%cR', ANSI_BACK_RED),
(r'%cg', ANSI_GREEN),
(r'%cG', ANSI_BACK_GREEN),
(r'%cy', ANSI_YELLOW),
(r'%cY', ANSI_BACK_YELLOW),
(r'%cb', ANSI_BLUE),
(r'%cB', ANSI_BACK_BLUE),
(r'%cm', ANSI_MAGENTA),
(r'%cM', ANSI_BACK_MAGENTA),
(r'%cc', ANSI_CYAN),
(r'%cC', ANSI_BACK_CYAN),
(r'%cw', ANSI_WHITE),
(r'%cW', ANSI_BACK_WHITE),
]
# Expanded mapping {r {n etc
hilite = ANSITable.ansi['hilite']
normal = ANSITable.ansi['normal']
hilite = ANSI_HILITE
normal = ANSI_NORMAL
self.ext_ansi_map = [
(r'{r', hilite + ANSITable.ansi['red']),
(r'{R', normal + ANSITable.ansi['red']),
(r'{g', hilite + ANSITable.ansi['green']),
(r'{G', normal + ANSITable.ansi['green']),
(r'{y', hilite + ANSITable.ansi['yellow']),
(r'{Y', normal + ANSITable.ansi['yellow']),
(r'{b', hilite + ANSITable.ansi['blue']),
(r'{B', normal + ANSITable.ansi['blue']),
(r'{m', hilite + ANSITable.ansi['magenta']),
(r'{M', normal + ANSITable.ansi['magenta']),
(r'{c', hilite + ANSITable.ansi['cyan']),
(r'{C', normal + ANSITable.ansi['cyan']),
(r'{w', hilite + ANSITable.ansi['white']), #white
(r'{W', normal + ANSITable.ansi['white']), #light grey
(r'{x', hilite + ANSITable.ansi['black']), #dark grey
(r'{X', normal + ANSITable.ansi['black']), #pure black
(r'{n', normal) #reset
(r'{r', hilite + ANSI_RED),
(r'{R', normal + ANSI_RED),
(r'{g', hilite + ANSI_GREEN),
(r'{G', normal + ANSI_GREEN),
(r'{y', hilite + ANSI_YELLOW),
(r'{Y', normal + ANSI_YELLOW),
(r'{b', hilite + ANSI_BLUE),
(r'{B', normal + ANSI_BLUE),
(r'{m', hilite + ANSI_MAGENTA),
(r'{M', normal + ANSI_MAGENTA),
(r'{c', hilite + ANSI_CYAN),
(r'{C', normal + ANSI_CYAN),
(r'{w', hilite + ANSI_WHITE), # pure white
(r'{W', normal + ANSI_WHITE), #light grey
(r'{x', hilite + ANSI_BLACK), #dark grey
(r'{X', normal + ANSI_BLACK), #pure black
(r'{n', normal) #reset
]
self.ansi_map = self.mux_ansi_map + self.ext_ansi_map
# xterm256
self.xterm256_map = [
(r'%c([1-5]{3})', self.parse_rgb),
(r'%c(b[1-5]{3})', self.parse_rgb),
(r'{([1-5]{3})', self.parse_rgb),
(r'{(b[1-5]{3})', self.parse_rgb)
]
# obs - order matters here, we want to do the xterms first since
# they collide with some of the other mappings otherwise.
self.ansi_map = self.xterm256_map + self.mux_ansi_map + self.ext_ansi_map
# prepare regex matching
self.ansi_sub = [(re.compile(sub[0], re.DOTALL), sub[1])
for sub in self.ansi_map]
# prepare matching ansi codes overall
self.ansi_regex = re.compile("\033\[[0-9;]+m")
def parse_ansi(self, string, strip_ansi=False):
def parse_rgb(self, rgbmatch):
"""
This is a replacer method called by re.sub with the matched
tag. It must return the correct ansi sequence.
It checks self.do_xterm256 to determine if conversion
to standard ansi should be done or not.
"""
if not rgbmatch:
return ""
rgbtag = rgbmatch.groups()[0]
background = rgbtag[0] == 'b'
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])
if self.do_xterm256:
colval = 16 + (red * 36) + (green * 6) + blue
return "\033[%s8;5;%s%s%sm" % (3 + int(background), colval/100, (colval%100)/10, colval%10)
else:
# xterm256 not supported, convert the rgb value to ansi instead
if red == green and red == blue and red < 2:
if background: return ANSI_BACK_BLACK
elif red >= 1: return ANSI_HILITE + ANSI_BLACK
else: return ANSO_NORMAL + ANSI_BLACK
elif red == green and red == blue:
if background: return ANSI_BACK_WHITE
elif red >= 4: return ANSI_HILITE + ANSI_WHITE
else: return ANSI_NORMAL + ANSI_WHITE
elif red > green and red > blue:
if background: return ANSI_BACK_RED
elif red >= 3: return ANSI_HILITE + ANSI_RED
else: return ANSI_NORMAL + ANSI_RED
elif red == green and red > blue:
if background: return ANSI_BACK_YELLOW
elif red >= 3: return ANSI_HILITE + ANSI_YELLOW
else: return ANSI_NORMAL + ANSI_YELLOW
elif red == blue and red > green:
if background: return ANSI_BACK_MAGENTA
elif red >= 3: return ANSI_HILITE + ANSI_MAGENTA
else: return ANSI_NORMAL + ANSI_MAGENTA
elif green > blue:
if background: return ANSI_BACK_GREEN
elif green >= 3: return ANSI_HILITE + ANSI_GREEN
else: return ANSI_NORMAL + ANSI_GREEN
elif green == blue:
if background: return ANSI_BACK_CYAN
elif green >= 3: return ANSI_HILITE + ANSI_CYAN
else: return ANSI_NORMAL + ANSI_CYAN
else: # mostly blue
if background: return ANSI_BACK_BLUE
elif blue >= 3: return ANSI_HILITE + ANSI_BLUE
else: return ANSI_NORMAL + ANSI_BLUE
def parse_ansi(self, string, strip_ansi=False, xterm256=False):
"""
Parses a string, subbing color codes according to
the stored mapping.
@ -142,6 +204,9 @@ class ANSIParser(object):
if not string:
return ''
string = utils.to_str(string)
self.do_xterm256 = xterm256
# handle all subs
for sub in self.ansi_sub:
# go through all available mappings and translate them
string = sub[0].sub(sub[1], string)
@ -150,7 +215,6 @@ class ANSIParser(object):
string = self.ansi_regex.sub("", string)
return string
ANSI_PARSER = ANSIParser()
@ -158,11 +222,11 @@ ANSI_PARSER = ANSIParser()
# Access function
#
def parse_ansi(string, strip_ansi=False, parser=ANSI_PARSER):
def parse_ansi(string, strip_ansi=False, parser=ANSI_PARSER, xterm256=False):
"""
Parses a string, subbing color codes as needed.
"""
return parser.parse_ansi(string, strip_ansi=strip_ansi)
return parser.parse_ansi(string, strip_ansi=strip_ansi, xterm256=xterm256)