Merged in some new lexer code. Older code will be phased out over time.
This code should split things more neatly than the current Pesterchum code, thus fixing a number of irritating bugs. Ideally, when finished, it will be easier and cleaner to work with as well.
This commit is contained in:
parent
6a34f769ed
commit
876e06f217
9 changed files with 1697 additions and 104 deletions
35
convo.py
35
convo.py
|
@ -12,6 +12,9 @@ from dataobjs import PesterProfile, PesterHistory
|
||||||
from generic import PesterIcon
|
from generic import PesterIcon
|
||||||
from parsetools import convertTags, lexMessage, splitMessage, mecmd, colorBegin, colorEnd, \
|
from parsetools import convertTags, lexMessage, splitMessage, mecmd, colorBegin, colorEnd, \
|
||||||
img2smiley, smiledict, oocre
|
img2smiley, smiledict, oocre
|
||||||
|
import parsetools
|
||||||
|
|
||||||
|
import pnc.lexercon as lexercon
|
||||||
|
|
||||||
class PesterTabWindow(QtGui.QFrame):
|
class PesterTabWindow(QtGui.QFrame):
|
||||||
def __init__(self, mainwindow, parent=None, convo="convo"):
|
def __init__(self, mainwindow, parent=None, convo="convo"):
|
||||||
|
@ -721,36 +724,12 @@ class PesterConvo(QtGui.QFrame):
|
||||||
|
|
||||||
@QtCore.pyqtSlot()
|
@QtCore.pyqtSlot()
|
||||||
def sentMessage(self):
|
def sentMessage(self):
|
||||||
|
# Offloaded to another function, like its sisters.
|
||||||
|
# Fetch the raw text from the input box.
|
||||||
|
text = self.textInput.text()
|
||||||
text = unicode(self.textInput.text())
|
text = unicode(self.textInput.text())
|
||||||
if text == "" or text[0:11] == "PESTERCHUM:":
|
|
||||||
return
|
|
||||||
oocDetected = oocre.match(text.strip())
|
|
||||||
if self.ooc and not oocDetected:
|
|
||||||
text = "(( %s ))" % (text)
|
|
||||||
self.history.add(text)
|
|
||||||
quirks = self.mainwindow.userprofile.quirks
|
|
||||||
lexmsg = lexMessage(text)
|
|
||||||
if type(lexmsg[0]) is not mecmd and self.applyquirks and not (self.ooc or oocDetected):
|
|
||||||
try:
|
|
||||||
lexmsg = quirks.apply(lexmsg)
|
|
||||||
except:
|
|
||||||
msgbox = QtGui.QMessageBox()
|
|
||||||
msgbox.setText("Whoa there! There seems to be a problem.")
|
|
||||||
msgbox.setInformativeText("A quirk seems to be having a problem. (Possibly you're trying to capture a non-existant group?)")
|
|
||||||
msgbox.exec_()
|
|
||||||
return
|
|
||||||
lexmsgs = splitMessage(lexmsg)
|
|
||||||
|
|
||||||
for lm in lexmsgs:
|
return parsetools.kxhandleInput(self, text, flavor="convo")
|
||||||
serverMsg = copy(lm)
|
|
||||||
self.addMessage(lm, True)
|
|
||||||
# if ceased, rebegin
|
|
||||||
if hasattr(self, 'chumopen') and not self.chumopen:
|
|
||||||
self.mainwindow.newConvoStarted.emit(QtCore.QString(self.title()), True)
|
|
||||||
self.setChumOpen(True)
|
|
||||||
text = convertTags(serverMsg, "ctag")
|
|
||||||
self.messageSent.emit(text, self.title())
|
|
||||||
self.textInput.setText("")
|
|
||||||
|
|
||||||
@QtCore.pyqtSlot()
|
@QtCore.pyqtSlot()
|
||||||
def addThisChum(self):
|
def addThisChum(self):
|
||||||
|
|
34
memos.py
34
memos.py
|
@ -10,6 +10,7 @@ from generic import PesterIcon, RightClickList, mysteryTime
|
||||||
from convo import PesterConvo, PesterInput, PesterText, PesterTabWindow
|
from convo import PesterConvo, PesterInput, PesterText, PesterTabWindow
|
||||||
from parsetools import convertTags, addTimeInitial, timeProtocol, \
|
from parsetools import convertTags, addTimeInitial, timeProtocol, \
|
||||||
lexMessage, colorBegin, colorEnd, mecmd, smiledict, oocre
|
lexMessage, colorBegin, colorEnd, mecmd, smiledict, oocre
|
||||||
|
import parsetools
|
||||||
from logviewer import PesterLogViewer
|
from logviewer import PesterLogViewer
|
||||||
|
|
||||||
def delta2txt(d, format="pc"):
|
def delta2txt(d, format="pc"):
|
||||||
|
@ -805,36 +806,9 @@ class PesterMemo(PesterConvo):
|
||||||
@QtCore.pyqtSlot()
|
@QtCore.pyqtSlot()
|
||||||
def sentMessage(self):
|
def sentMessage(self):
|
||||||
text = unicode(self.textInput.text())
|
text = unicode(self.textInput.text())
|
||||||
if text == "" or text[0:11] == "PESTERCHUM:":
|
|
||||||
return
|
return parsetools.kxhandleInput(self, text, flavor="memos")
|
||||||
oocDetected = oocre.match(text.strip())
|
|
||||||
if self.ooc and not oocDetected:
|
|
||||||
text = "(( %s ))" % (text)
|
|
||||||
self.history.add(text)
|
|
||||||
if self.time.getTime() == None:
|
|
||||||
self.sendtime()
|
|
||||||
grammar = self.time.getGrammar()
|
|
||||||
quirks = self.mainwindow.userprofile.quirks
|
|
||||||
lexmsg = lexMessage(text)
|
|
||||||
if type(lexmsg[0]) is not mecmd:
|
|
||||||
if self.applyquirks and not (self.ooc or oocDetected):
|
|
||||||
lexmsg = quirks.apply(lexmsg)
|
|
||||||
initials = self.mainwindow.profile().initials()
|
|
||||||
colorcmd = self.mainwindow.profile().colorcmd()
|
|
||||||
clientMsg = [colorBegin("<c=%s>" % (colorcmd), colorcmd),
|
|
||||||
"%s%s%s: " % (grammar.pcf, initials, grammar.number)] + lexmsg + [colorEnd("</c>")]
|
|
||||||
# account for TC's parsing error
|
|
||||||
serverMsg = [colorBegin("<c=%s>" % (colorcmd), colorcmd),
|
|
||||||
"%s: " % (initials)] + lexmsg + [colorEnd("</c>"), " "]
|
|
||||||
else:
|
|
||||||
clientMsg = copy(lexmsg)
|
|
||||||
serverMsg = copy(lexmsg)
|
|
||||||
|
|
||||||
self.addMessage(clientMsg, True)
|
|
||||||
serverText = convertTags(serverMsg, "ctag")
|
|
||||||
self.messageSent.emit(serverText, self.title())
|
|
||||||
|
|
||||||
self.textInput.setText("")
|
|
||||||
@QtCore.pyqtSlot(QtCore.QString)
|
@QtCore.pyqtSlot(QtCore.QString)
|
||||||
def namesUpdated(self, channel):
|
def namesUpdated(self, channel):
|
||||||
c = unicode(channel)
|
c = unicode(channel)
|
||||||
|
|
25
menus.py
25
menus.py
|
@ -7,6 +7,8 @@ from dataobjs import pesterQuirk, PesterProfile
|
||||||
from memos import TimeSlider, TimeInput
|
from memos import TimeSlider, TimeInput
|
||||||
from version import _pcVersion
|
from version import _pcVersion
|
||||||
|
|
||||||
|
import parsetools
|
||||||
|
|
||||||
_datadir = ostools.getDataDir()
|
_datadir = ostools.getDataDir()
|
||||||
|
|
||||||
class PesterQuirkItem(QtGui.QTreeWidgetItem):
|
class PesterQuirkItem(QtGui.QTreeWidgetItem):
|
||||||
|
@ -245,28 +247,9 @@ class QuirkTesterWindow(QtGui.QDialog):
|
||||||
@QtCore.pyqtSlot()
|
@QtCore.pyqtSlot()
|
||||||
def sentMessage(self):
|
def sentMessage(self):
|
||||||
text = unicode(self.textInput.text())
|
text = unicode(self.textInput.text())
|
||||||
if text == "" or text[0:11] == "PESTERCHUM:":
|
|
||||||
return
|
|
||||||
self.history.add(text)
|
|
||||||
quirks = pesterQuirks(self.parent().testquirks())
|
|
||||||
lexmsg = lexMessage(text)
|
|
||||||
if type(lexmsg[0]) is not mecmd:
|
|
||||||
try:
|
|
||||||
lexmsg = quirks.apply(lexmsg)
|
|
||||||
except Exception, e:
|
|
||||||
msgbox = QtGui.QMessageBox()
|
|
||||||
msgbox.setText("Whoa there! There seems to be a problem.")
|
|
||||||
msgbox.setInformativeText("A quirk seems to be having a problem. (Possibly you're trying to capture a non-existant group?)\n\
|
|
||||||
%s" % e)
|
|
||||||
msgbox.exec_()
|
|
||||||
return
|
|
||||||
lexmsgs = splitMessage(lexmsg)
|
|
||||||
|
|
||||||
for lm in lexmsgs:
|
return parsetools.kxhandleInput(self, text, "menus")
|
||||||
serverMsg = copy(lm)
|
|
||||||
self.addMessage(lm, True)
|
|
||||||
text = convertTags(serverMsg, "ctag")
|
|
||||||
self.textInput.setText("")
|
|
||||||
def addMessage(self, msg, me=True):
|
def addMessage(self, msg, me=True):
|
||||||
if type(msg) in [str, unicode]:
|
if type(msg) in [str, unicode]:
|
||||||
lexmsg = lexMessage(msg)
|
lexmsg = lexMessage(msg)
|
||||||
|
|
470
parsetools.py
470
parsetools.py
|
@ -1,15 +1,21 @@
|
||||||
import re
|
import re
|
||||||
import random
|
import random
|
||||||
import ostools
|
import ostools
|
||||||
|
import collections
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from PyQt4 import QtGui
|
from PyQt4 import QtGui, QtCore
|
||||||
|
|
||||||
from generic import mysteryTime
|
from generic import mysteryTime
|
||||||
from quirks import ScriptQuirks
|
from quirks import ScriptQuirks
|
||||||
from pyquirks import PythonQuirks
|
from pyquirks import PythonQuirks
|
||||||
from luaquirks import LuaQuirks
|
from luaquirks import LuaQuirks
|
||||||
|
|
||||||
|
# karxi: My own contribution to this - a proper lexer.
|
||||||
|
import pnc.lexercon as lexercon
|
||||||
|
|
||||||
|
# I'll clean up the things that are no longer needed once the transition is
|
||||||
|
# actually finished.
|
||||||
_ctag_begin = re.compile(r'(?i)<c=(.*?)>')
|
_ctag_begin = re.compile(r'(?i)<c=(.*?)>')
|
||||||
_gtag_begin = re.compile(r'(?i)<g[a-f]>')
|
_gtag_begin = re.compile(r'(?i)<g[a-f]>')
|
||||||
_ctag_end = re.compile(r'(?i)</c>')
|
_ctag_end = re.compile(r'(?i)</c>')
|
||||||
|
@ -61,7 +67,10 @@ def lexer(string, objlist):
|
||||||
stringlist = copy(newstringlist)
|
stringlist = copy(newstringlist)
|
||||||
return stringlist
|
return stringlist
|
||||||
|
|
||||||
class colorBegin(object):
|
# karxi: All of these were derived from object before. I changed them to
|
||||||
|
# lexercon.Chunk so that I'd have an easier way to match against them until
|
||||||
|
# they're redone/removed.
|
||||||
|
class colorBegin(lexercon.Chunk):
|
||||||
def __init__(self, string, color):
|
def __init__(self, string, color):
|
||||||
self.string = string
|
self.string = string
|
||||||
self.color = color
|
self.color = color
|
||||||
|
@ -87,7 +96,8 @@ class colorBegin(object):
|
||||||
elif format == "ctag":
|
elif format == "ctag":
|
||||||
(r,g,b,a) = qc.getRgb()
|
(r,g,b,a) = qc.getRgb()
|
||||||
return '<c=%s,%s,%s>' % (r,g,b)
|
return '<c=%s,%s,%s>' % (r,g,b)
|
||||||
class colorEnd(object):
|
|
||||||
|
class colorEnd(lexercon.Chunk):
|
||||||
def __init__(self, string):
|
def __init__(self, string):
|
||||||
self.string = string
|
self.string = string
|
||||||
def convert(self, format):
|
def convert(self, format):
|
||||||
|
@ -99,7 +109,8 @@ class colorEnd(object):
|
||||||
return ""
|
return ""
|
||||||
else:
|
else:
|
||||||
return self.string
|
return self.string
|
||||||
class formatBegin(object):
|
|
||||||
|
class formatBegin(lexercon.Chunk):
|
||||||
def __init__(self, string, ftype):
|
def __init__(self, string, ftype):
|
||||||
self.string = string
|
self.string = string
|
||||||
self.ftype = ftype
|
self.ftype = ftype
|
||||||
|
@ -112,7 +123,8 @@ class formatBegin(object):
|
||||||
return ""
|
return ""
|
||||||
else:
|
else:
|
||||||
return self.string
|
return self.string
|
||||||
class formatEnd(object):
|
|
||||||
|
class formatEnd(lexercon.Chunk):
|
||||||
def __init__(self, string, ftype):
|
def __init__(self, string, ftype):
|
||||||
self.string = string
|
self.string = string
|
||||||
self.ftype = ftype
|
self.ftype = ftype
|
||||||
|
@ -125,7 +137,8 @@ class formatEnd(object):
|
||||||
return ""
|
return ""
|
||||||
else:
|
else:
|
||||||
return self.string
|
return self.string
|
||||||
class hyperlink(object):
|
|
||||||
|
class hyperlink(lexercon.Chunk):
|
||||||
def __init__(self, string):
|
def __init__(self, string):
|
||||||
self.string = string
|
self.string = string
|
||||||
def convert(self, format):
|
def convert(self, format):
|
||||||
|
@ -135,10 +148,12 @@ class hyperlink(object):
|
||||||
return "[url]%s[/url]" % (self.string)
|
return "[url]%s[/url]" % (self.string)
|
||||||
else:
|
else:
|
||||||
return self.string
|
return self.string
|
||||||
|
|
||||||
class hyperlink_lazy(hyperlink):
|
class hyperlink_lazy(hyperlink):
|
||||||
def __init__(self, string):
|
def __init__(self, string):
|
||||||
self.string = "http://" + string
|
self.string = "http://" + string
|
||||||
class imagelink(object):
|
|
||||||
|
class imagelink(lexercon.Chunk):
|
||||||
def __init__(self, string, img):
|
def __init__(self, string, img):
|
||||||
self.string = string
|
self.string = string
|
||||||
self.img = img
|
self.img = img
|
||||||
|
@ -152,7 +167,8 @@ class imagelink(object):
|
||||||
return ""
|
return ""
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
class memolex(object):
|
|
||||||
|
class memolex(lexercon.Chunk):
|
||||||
def __init__(self, string, space, channel):
|
def __init__(self, string, space, channel):
|
||||||
self.string = string
|
self.string = string
|
||||||
self.space = space
|
self.space = space
|
||||||
|
@ -162,7 +178,8 @@ class memolex(object):
|
||||||
return "%s<a href='%s'>%s</a>" % (self.space, self.channel, self.channel)
|
return "%s<a href='%s'>%s</a>" % (self.space, self.channel, self.channel)
|
||||||
else:
|
else:
|
||||||
return self.string
|
return self.string
|
||||||
class chumhandlelex(object):
|
|
||||||
|
class chumhandlelex(lexercon.Chunk):
|
||||||
def __init__(self, string, space, handle):
|
def __init__(self, string, space, handle):
|
||||||
self.string = string
|
self.string = string
|
||||||
self.space = space
|
self.space = space
|
||||||
|
@ -172,7 +189,8 @@ class chumhandlelex(object):
|
||||||
return "%s<a href='%s'>%s</a>" % (self.space, self.handle, self.handle)
|
return "%s<a href='%s'>%s</a>" % (self.space, self.handle, self.handle)
|
||||||
else:
|
else:
|
||||||
return self.string
|
return self.string
|
||||||
class smiley(object):
|
|
||||||
|
class smiley(lexercon.Chunk):
|
||||||
def __init__(self, string):
|
def __init__(self, string):
|
||||||
self.string = string
|
self.string = string
|
||||||
def convert(self, format):
|
def convert(self, format):
|
||||||
|
@ -180,7 +198,8 @@ class smiley(object):
|
||||||
return "<img src='smilies/%s' alt='%s' title='%s' />" % (smiledict[self.string], self.string, self.string)
|
return "<img src='smilies/%s' alt='%s' title='%s' />" % (smiledict[self.string], self.string, self.string)
|
||||||
else:
|
else:
|
||||||
return self.string
|
return self.string
|
||||||
class honker(object):
|
|
||||||
|
class honker(lexercon.Chunk):
|
||||||
def __init__(self, string):
|
def __init__(self, string):
|
||||||
self.string = string
|
self.string = string
|
||||||
def convert(self, format):
|
def convert(self, format):
|
||||||
|
@ -188,13 +207,29 @@ class honker(object):
|
||||||
return "<img src='smilies/honk.png' alt'honk' title='honk' />"
|
return "<img src='smilies/honk.png' alt'honk' title='honk' />"
|
||||||
else:
|
else:
|
||||||
return self.string
|
return self.string
|
||||||
class mecmd(object):
|
|
||||||
|
class mecmd(lexercon.Chunk):
|
||||||
def __init__(self, string, mecmd, suffix):
|
def __init__(self, string, mecmd, suffix):
|
||||||
self.string = string
|
self.string = string
|
||||||
self.suffix = suffix
|
self.suffix = suffix
|
||||||
def convert(self, format):
|
def convert(self, format):
|
||||||
return self.string
|
return self.string
|
||||||
|
|
||||||
|
kxpclexer = lexercon.Pesterchum()
|
||||||
|
|
||||||
|
def kxlexMsg(string):
|
||||||
|
# Do a bit of sanitization.
|
||||||
|
# TODO: Let people paste line-by-line normally.
|
||||||
|
msg = string.replace('\n', ' ').replace('\r', ' ')
|
||||||
|
# Something the original doesn't seem to have accounted for.
|
||||||
|
# Replace tabs with 4 spaces.
|
||||||
|
msg = msg.replace('\t', ' ' * 4)
|
||||||
|
msg = unicode(string)
|
||||||
|
# Begin lexing.
|
||||||
|
msg = kxpclexer.lex(msg)
|
||||||
|
# ...and that's it for this.
|
||||||
|
return msg
|
||||||
|
|
||||||
def lexMessage(string):
|
def lexMessage(string):
|
||||||
lexlist = [(mecmd, _mecmdre),
|
lexlist = [(mecmd, _mecmdre),
|
||||||
(colorBegin, _ctag_begin), (colorBegin, _gtag_begin),
|
(colorBegin, _ctag_begin), (colorBegin, _gtag_begin),
|
||||||
|
@ -262,7 +297,7 @@ def _max_msg_len(mask=None, target=None):
|
||||||
# Pesterchum.
|
# Pesterchum.
|
||||||
# Note that this effectively assumes the worst when not provided the
|
# Note that this effectively assumes the worst when not provided the
|
||||||
# information it needs to get an accurate read, so later on, it'll need to
|
# information it needs to get an accurate read, so later on, it'll need to
|
||||||
# be given a nick and the user's hostmask, as well as where the message is
|
# be given a nick or the user's hostmask, as well as where the message is
|
||||||
# being sent.
|
# being sent.
|
||||||
# It effectively has to construct the message that'll be sent in advance.
|
# It effectively has to construct the message that'll be sent in advance.
|
||||||
limit = 512
|
limit = 512
|
||||||
|
@ -299,46 +334,259 @@ def _max_msg_len(mask=None, target=None):
|
||||||
|
|
||||||
return limit
|
return limit
|
||||||
|
|
||||||
|
def kxsplitMsg(lexed, fmt="pchum", maxlen=None, debug=False):
|
||||||
|
"""Split messages so that they don't go over the length limit.
|
||||||
|
Returns a list of the messages, neatly split.
|
||||||
|
|
||||||
|
Keep in mind that there's a little bit of magic involved in this at the
|
||||||
|
moment; some unsafe assumptions are made."""
|
||||||
|
# Procedure: Lex. Convert for lengths as we go, keep starting tag
|
||||||
|
# length as we go too. Split whenever we hit the limit, add the tags to
|
||||||
|
# the start of the next line (or just keep a running line length
|
||||||
|
# total), and continue.
|
||||||
|
# N.B.: Keep the end tag length too. (+4 for each.)
|
||||||
|
# Copy the list so we can't break anything.
|
||||||
|
lexed = list(lexed)
|
||||||
|
working = []
|
||||||
|
output = []
|
||||||
|
open_ctags = []
|
||||||
|
# Number of characters we've used.
|
||||||
|
curlen = 0
|
||||||
|
# Maximum number of characters *to* use.
|
||||||
|
if not maxlen:
|
||||||
|
maxlen = _max_msg_len()
|
||||||
|
elif maxlen < 0:
|
||||||
|
# Subtract the (negative) length, giving us less leeway in this
|
||||||
|
# function.
|
||||||
|
maxlen = _max_msg_len() + maxlen
|
||||||
|
|
||||||
|
# Defined here, but modified in the loop.
|
||||||
|
msglen = 0
|
||||||
|
|
||||||
|
def efflenleft():
|
||||||
|
"""Get the remaining space we have to work with, accounting for closing
|
||||||
|
tags that will be needed."""
|
||||||
|
return maxlen - curlen - (len(open_ctags) * 4)
|
||||||
|
|
||||||
|
safekeeping = lexed[:]
|
||||||
|
lexed = collections.deque(lexed)
|
||||||
|
rounds = 0
|
||||||
|
while len(lexed) > 0:
|
||||||
|
rounds += 1
|
||||||
|
if debug:
|
||||||
|
print "[Starting round {}...]".format(rounds)
|
||||||
|
msg = lexed.popleft()
|
||||||
|
msglen = 0
|
||||||
|
is_text = False
|
||||||
|
text_preproc = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
msglen = len(msg.convert(fmt))
|
||||||
|
except AttributeError:
|
||||||
|
# It's probably not a lexer tag. Assume a string.
|
||||||
|
# The input to this is supposed to be sanitary, after all.
|
||||||
|
msglen = len(msg)
|
||||||
|
# We allow this to error out if it fails for some reason.
|
||||||
|
# Remind us that it's a string, and thus can be split.
|
||||||
|
is_text = True
|
||||||
|
|
||||||
|
# Test if we have room.
|
||||||
|
if msglen > efflenleft():
|
||||||
|
# We do NOT have room - which means we need to think of how to
|
||||||
|
# handle this.
|
||||||
|
# If we have text, we can split it, keeping color codes in mind.
|
||||||
|
# Since those were already parsed, we don't have to worry about
|
||||||
|
# breaking something that way.
|
||||||
|
# Thus, we can split it, finalize it, and add the remainder to the
|
||||||
|
# next line (after the color codes).
|
||||||
|
if is_text and efflenleft() > 30:
|
||||||
|
text_preproc = True
|
||||||
|
# We use 30 as a general 'guess' - if there's less space than
|
||||||
|
# that, it's probably not worth trying to cram text in.
|
||||||
|
# This also saves us from infinitely trying to reduce the size
|
||||||
|
# of the input.
|
||||||
|
stack = []
|
||||||
|
# We have text to split.
|
||||||
|
# This is okay because we don't apply the changes until the
|
||||||
|
# end - and the rest is shoved onto the stack to be dealt with
|
||||||
|
# immediately after.
|
||||||
|
lenl = efflenleft()
|
||||||
|
subround = 0
|
||||||
|
while len(msg) > lenl:
|
||||||
|
subround += 1
|
||||||
|
if debug:
|
||||||
|
print "[Splitting round {}-{}...]".format(
|
||||||
|
rounds, subround
|
||||||
|
)
|
||||||
|
point = msg.rfind(' ', 0, lenl)
|
||||||
|
if point < 0:
|
||||||
|
# No spaces to break on...ugh. Break at the last space
|
||||||
|
# we can instead.
|
||||||
|
point = lenl ## - 1
|
||||||
|
# NOTE: The - 1 is for safety (but probably isn't
|
||||||
|
# actually necessary.)
|
||||||
|
# Split and push what we have.
|
||||||
|
stack.append(msg[:point])
|
||||||
|
# Remove what we just added.
|
||||||
|
msg = msg[point:]
|
||||||
|
if debug:
|
||||||
|
print "msg = {!r}".format(msg)
|
||||||
|
else:
|
||||||
|
# Catch the remainder.
|
||||||
|
stack.append(msg)
|
||||||
|
if debug:
|
||||||
|
print "msg caught; stack = {!r}".format(stack)
|
||||||
|
# Done processing. Pluck out the first portion so we can
|
||||||
|
# continue processing, then add the rest to our waiting list.
|
||||||
|
msg = stack.pop(0)
|
||||||
|
msglen = len(msg)
|
||||||
|
# Now we have a separated list, so we can add it.
|
||||||
|
# First we have to reverse it, because the extendleft method of
|
||||||
|
# deque objects - like our lexed queue - inserts the elements
|
||||||
|
# *backwards*....
|
||||||
|
stack.reverse()
|
||||||
|
# Now we put them on 'top' of the proverbial deck, and deal
|
||||||
|
# with them next round.
|
||||||
|
lexed.extendleft(stack)
|
||||||
|
# We'll deal with those later. Now to get the 'msg' on the
|
||||||
|
# working list and finalize it for output - which really just
|
||||||
|
# means forcing the issue....
|
||||||
|
working.append(msg)
|
||||||
|
curlen += msglen
|
||||||
|
|
||||||
|
# Clear the slate. Add the remaining ctags, then add working to
|
||||||
|
# output, then clear working and statistics. Then we can move on to
|
||||||
|
# append as normal.
|
||||||
|
# Keep in mind that any open ctags get added to the beginning of
|
||||||
|
# working again, since they're still open!
|
||||||
|
|
||||||
|
# ...
|
||||||
|
# ON SECOND THOUGHT: The lexer balances for us, so let's just use
|
||||||
|
# that for now. I can split up the function for this later.
|
||||||
|
working = ''.join(kxpclexer.list_convert(working))
|
||||||
|
working = kxpclexer.lex(working)
|
||||||
|
working = ''.join(kxpclexer.list_convert(working))
|
||||||
|
# TODO: Is that lazy? Yes. This is a modification made to test if
|
||||||
|
# it'll work, *not* if it'll be efficient.
|
||||||
|
|
||||||
|
# Now that it's done the work for us, append and resume.
|
||||||
|
output.append(working)
|
||||||
|
# Reset working, starting it with the unclosed ctags.
|
||||||
|
working = open_ctags[:]
|
||||||
|
# Calculate the length of the starting tags, add it before anything
|
||||||
|
# else.
|
||||||
|
curlen = sum(len(tag.convert(fmt)) for tag in working)
|
||||||
|
if text_preproc:
|
||||||
|
# If we got here, it means we overflowed due to text - which
|
||||||
|
# means we also split and added it to working. There's no
|
||||||
|
# reason to continue and add it twice.
|
||||||
|
# This could be handled with an elif chain, but eh.
|
||||||
|
continue
|
||||||
|
# If we got here, it means we haven't done anything with 'msg' yet,
|
||||||
|
# in spite of popping it from lexed, so add it back for the next
|
||||||
|
# round.
|
||||||
|
# This sends it through for another round of splitting and work,
|
||||||
|
# possibly.
|
||||||
|
lexed.appendleft(msg)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Normal tag processing stuff. Considerably less interesting/intensive
|
||||||
|
# than the text processing we did up there.
|
||||||
|
if isinstance(msg, lexercon.CTagEnd):
|
||||||
|
# Check for Ends first (subclassing issue).
|
||||||
|
if len(open_ctags) > 0:
|
||||||
|
# Don't add it unless it's going to make things /more/ even.
|
||||||
|
# We could have a Strict checker that errors on that, who
|
||||||
|
# knows.
|
||||||
|
# We just closed a ctag.
|
||||||
|
open_ctags.pop()
|
||||||
|
else:
|
||||||
|
# Ignore it.
|
||||||
|
# NOTE: I realize this is going to screw up something I do, but
|
||||||
|
# it also stops us from screwing up Chumdroid, so...whatever.
|
||||||
|
continue
|
||||||
|
elif isinstance(msg, lexercon.CTag):
|
||||||
|
# It's an opening color tag!
|
||||||
|
open_ctags.append(msg)
|
||||||
|
|
||||||
|
# Add it to the working message.
|
||||||
|
working.append(msg)
|
||||||
|
|
||||||
|
# Record the additional length.
|
||||||
|
# Also, need to be sure to account for the ends that would be added.
|
||||||
|
curlen += msglen
|
||||||
|
else:
|
||||||
|
# Once we're finally out of things to add, we're, well...out.
|
||||||
|
# So add working to the result one last time.
|
||||||
|
working = ''.join(kxpclexer.list_convert(working))
|
||||||
|
output.append(working)
|
||||||
|
|
||||||
|
# We're...done?
|
||||||
|
return output
|
||||||
|
|
||||||
def splitMessage(msg, format="ctag"):
|
def splitMessage(msg, format="ctag"):
|
||||||
"""Splits message if it is too long."""
|
"""Splits message if it is too long.
|
||||||
|
This is the older version of this function, kept for compatibility.
|
||||||
|
It will eventually be phased out."""
|
||||||
# split long text lines
|
# split long text lines
|
||||||
buf = []
|
buf = []
|
||||||
for o in msg:
|
for o in msg:
|
||||||
if type(o) in [str, unicode] and len(o) > 200:
|
if type(o) in [str, unicode] and len(o) > 200:
|
||||||
|
# Split with a step of 200. I.e., cut long segments into chunks of
|
||||||
|
# 200 characters.
|
||||||
|
# I'm...not sure why this is done. I'll probably factor it out
|
||||||
|
# later on.
|
||||||
for i in range(0, len(o), 200):
|
for i in range(0, len(o), 200):
|
||||||
buf.append(o[i:i+200])
|
buf.append(o[i:i+200])
|
||||||
else:
|
else:
|
||||||
|
# Add non-text tags or 'short' segments without processing.
|
||||||
buf.append(o)
|
buf.append(o)
|
||||||
msg = buf
|
# Copy the iterative variable.
|
||||||
okmsg = []
|
msg = list(buf)
|
||||||
|
# This is the working segment.
|
||||||
|
working = []
|
||||||
|
# Keep a stack of open color tags.
|
||||||
cbegintags = []
|
cbegintags = []
|
||||||
|
# This is the final result.
|
||||||
output = []
|
output = []
|
||||||
|
print repr(msg)
|
||||||
for o in msg:
|
for o in msg:
|
||||||
oldctag = None
|
oldctag = None
|
||||||
okmsg.append(o)
|
# Add to the working segment.
|
||||||
|
working.append(o)
|
||||||
if type(o) is colorBegin:
|
if type(o) is colorBegin:
|
||||||
|
# Track the open tag.
|
||||||
cbegintags.append(o)
|
cbegintags.append(o)
|
||||||
elif type(o) is colorEnd:
|
elif type(o) is colorEnd:
|
||||||
try:
|
try:
|
||||||
|
# Remove the last open tag, since we've closed it.
|
||||||
oldctag = cbegintags.pop()
|
oldctag = cbegintags.pop()
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
|
# THIS part is the part I don't get. I'll revise it later....
|
||||||
|
# It doesn't seem to catch ending ctags properly...or beginning ones.
|
||||||
|
# It's pretty much just broken, likely due to the line below.
|
||||||
|
# Maybe I can convert the tags, save the beginning tags, check their
|
||||||
|
# lengths and apply them after a split - or even iterate over each set,
|
||||||
|
# applying old tags before continuing...I don't know.
|
||||||
# yeah normally i'd do binary search but im lazy
|
# yeah normally i'd do binary search but im lazy
|
||||||
msglen = len(convertTags(okmsg, format)) + 4*(len(cbegintags))
|
# Get length of beginning tags, and the end tags that'd be applied.
|
||||||
|
msglen = len(convertTags(working, format)) + 4*(len(cbegintags))
|
||||||
|
# Previously this used 400.
|
||||||
if msglen > _max_msg_len():
|
if msglen > _max_msg_len():
|
||||||
okmsg.pop()
|
working.pop()
|
||||||
if type(o) is colorBegin:
|
if type(o) is colorBegin:
|
||||||
cbegintags.pop()
|
cbegintags.pop()
|
||||||
elif type(o) is colorEnd and oldctag is not None:
|
elif type(o) is colorEnd and oldctag is not None:
|
||||||
cbegintags.append(oldctag)
|
cbegintags.append(oldctag)
|
||||||
if len(okmsg) == 0:
|
if len(working) == 0:
|
||||||
output.append([o])
|
output.append([o])
|
||||||
else:
|
else:
|
||||||
tmp = []
|
tmp = []
|
||||||
for color in cbegintags:
|
for color in cbegintags:
|
||||||
okmsg.append(colorEnd("</c>"))
|
working.append(colorEnd("</c>"))
|
||||||
tmp.append(color)
|
tmp.append(color)
|
||||||
output.append(okmsg)
|
output.append(working)
|
||||||
if type(o) is colorBegin:
|
if type(o) is colorBegin:
|
||||||
cbegintags.append(o)
|
cbegintags.append(o)
|
||||||
elif type(o) is colorEnd:
|
elif type(o) is colorEnd:
|
||||||
|
@ -347,12 +595,183 @@ def splitMessage(msg, format="ctag"):
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
tmp.append(o)
|
tmp.append(o)
|
||||||
okmsg = tmp
|
working = tmp
|
||||||
|
|
||||||
if len(okmsg) > 0:
|
if len(working) > 0:
|
||||||
output.append(okmsg)
|
# Add any stragglers.
|
||||||
|
output.append(working)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
def kxhandleInput(ctx, text=None, flavor=None):
|
||||||
|
"""The function that user input that should be sent to the server is routed
|
||||||
|
through. Handles lexing, splitting, and quirk application."""
|
||||||
|
# Flavor is important for logic, ctx is 'self'.
|
||||||
|
# Flavors are 'convo', 'menus', and 'memos' - so named after the source
|
||||||
|
# files for the original sentMessage variants.
|
||||||
|
|
||||||
|
if flavor is None:
|
||||||
|
return ValueError("A flavor is needed to determine suitable logic!")
|
||||||
|
|
||||||
|
if text is None:
|
||||||
|
# Fetch the raw text from the input box.
|
||||||
|
text = ctx.textInput.text()
|
||||||
|
text = unicode(ctx.textInput.text())
|
||||||
|
|
||||||
|
# Preprocessing stuff.
|
||||||
|
if text == "" or text.startswith("PESTERCHUM:"):
|
||||||
|
# We don't allow users to send system messages. There's also no
|
||||||
|
# point if they haven't entered anything.
|
||||||
|
# TODO: Consider accounting for the single-space bug, 'beloved'
|
||||||
|
# though it is.
|
||||||
|
return
|
||||||
|
|
||||||
|
# Add the *raw* text to our history.
|
||||||
|
ctx.history.add(text)
|
||||||
|
|
||||||
|
if flavor != "menus":
|
||||||
|
# Check if the line is OOC. Note that Pesterchum *is* kind enough to strip
|
||||||
|
# trailing spaces for us, even in the older versions.
|
||||||
|
oocDetected = oocre.match(text.strip())
|
||||||
|
is_ooc = ctx.ooc or oocDetected
|
||||||
|
if ctx.ooc and not oocDetected:
|
||||||
|
# If we're supposed to be OOC, apply it artificially.
|
||||||
|
text = "(( %s ))" % (text)
|
||||||
|
# Also, quirk stuff.
|
||||||
|
should_quirk = ctx.applyquirks
|
||||||
|
else:
|
||||||
|
# 'menus' means a quirk tester window, which doesn't have an OOC
|
||||||
|
# variable.
|
||||||
|
is_ooc = False
|
||||||
|
should_quirk = True
|
||||||
|
is_action = text.startswith("/me")
|
||||||
|
|
||||||
|
# Begin message processing.
|
||||||
|
msg = text
|
||||||
|
# We use 'text' despite its lack of processing because it's simpler.
|
||||||
|
if should_quirk and not (is_action or is_ooc):
|
||||||
|
# Fetch the quirks we'll have to apply.
|
||||||
|
quirks = ctx.mainwindow.userprofile.quirks
|
||||||
|
try:
|
||||||
|
# Do quirk things. (Ugly, but it'll have to do for now.)
|
||||||
|
# TODO: Look into the quirk system, modify/redo it.
|
||||||
|
# Gotta encapsulate or we might parse the wrong way.
|
||||||
|
msg = quirks.apply([msg])
|
||||||
|
except Exception as err:
|
||||||
|
# Tell the user we couldn't do quirk things.
|
||||||
|
# TODO: Include the actual error...and the quirk it came from?
|
||||||
|
msgbox = QtGui.QMessageBox()
|
||||||
|
msgbox.setText("Whoa there! There seems to be a problem.")
|
||||||
|
err_info = "A quirk seems to be having a problem. (Error: {!s})"
|
||||||
|
err_info = err_info.format(err)
|
||||||
|
msgbox.setInformativeText(err_info)
|
||||||
|
msgbox.exec_()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Debug output.
|
||||||
|
print msg
|
||||||
|
# karxi: We have a list...but I'm not sure if we ever get anything else, so
|
||||||
|
# best to play it safe. I may remove this during later refactoring.
|
||||||
|
if isinstance(msg, list):
|
||||||
|
for i, m in enumerate(msg):
|
||||||
|
if isinstance(m, lexercon.Chunk):
|
||||||
|
# NOTE: KLUDGE. Filters out old PChum objects.
|
||||||
|
# karxi: This only works because I went in and subtyped them to
|
||||||
|
# an object type I provided - just so I could pluck them out
|
||||||
|
# later.
|
||||||
|
msg[i] = m.convert(format="ctag")
|
||||||
|
msg = ''.join(msg)
|
||||||
|
|
||||||
|
# Quirks have been applied. Lex the messages (finally).
|
||||||
|
msg = kxlexMsg(msg)
|
||||||
|
|
||||||
|
# Remove coloring if this is a /me!
|
||||||
|
if is_action:
|
||||||
|
# Filter out formatting specifiers (just ctags, at the moment).
|
||||||
|
msg = filter(
|
||||||
|
lambda m: not isinstance(m,
|
||||||
|
(lexercon.CTag, lexercon.CTagEnd)
|
||||||
|
),
|
||||||
|
msg
|
||||||
|
)
|
||||||
|
# We'll also add /me to the beginning of any new messages, later.
|
||||||
|
|
||||||
|
# Put what's necessary in before splitting.
|
||||||
|
# Fetch our time if we're producing this for a memo.
|
||||||
|
if flavor == "memos":
|
||||||
|
if ctx.time.getTime() == None:
|
||||||
|
ctx.sendtime()
|
||||||
|
grammar = ctx.time.getGrammar()
|
||||||
|
# Oh, great...there's a parsing error to work around. Times are added
|
||||||
|
# automatically when received, but not when added directly?... I'll
|
||||||
|
# have to unify that.
|
||||||
|
# TODO: Fix parsing disparity.
|
||||||
|
initials = ctx.mainwindow.profile().initials()
|
||||||
|
colorcmd = ctx.mainwindow.profile().colorcmd()
|
||||||
|
# We'll use those later.
|
||||||
|
|
||||||
|
# Split the messages so we don't go over the buffer and lose text.
|
||||||
|
maxlen = _max_msg_len()
|
||||||
|
# Since we have to do some post-processing, we need to adjust the maximum
|
||||||
|
# length we can use.
|
||||||
|
if flavor == "convo":
|
||||||
|
# The old Pesterchum setup used 200 for this.
|
||||||
|
maxlen = 300
|
||||||
|
elif flavor == "memos":
|
||||||
|
# Use the max, with some room added so we can make additions.
|
||||||
|
maxlen -= 20
|
||||||
|
|
||||||
|
# Split the message. (Finally.)
|
||||||
|
# This is also set up to parse it into strings.
|
||||||
|
lexmsgs = kxsplitMsg(msg, "pchum", maxlen=maxlen)
|
||||||
|
# Strip off the excess.
|
||||||
|
for i, m in enumerate(lexmsgs):
|
||||||
|
lexmsgs[i] = m.strip()
|
||||||
|
|
||||||
|
# Pester message handling.
|
||||||
|
if flavor == "convo":
|
||||||
|
# if ceased, rebegin
|
||||||
|
if hasattr(ctx, 'chumopen') and not ctx.chumopen:
|
||||||
|
ctx.mainwindow.newConvoStarted.emit(
|
||||||
|
QtCore.QString(ctx.title()), True
|
||||||
|
)
|
||||||
|
ctx.setChumOpen(True)
|
||||||
|
|
||||||
|
# Post-process and send the messages.
|
||||||
|
for i, lm in enumerate(lexmsgs):
|
||||||
|
# If we're working with an action and we split, it should have /mes.
|
||||||
|
if is_action and i > 0:
|
||||||
|
# Add them post-split.
|
||||||
|
lm = u"/me " + lm
|
||||||
|
# NOTE: No reason to reassign for now, but...why not?
|
||||||
|
lexmsgs[i] = lm
|
||||||
|
|
||||||
|
# Copy the lexed result.
|
||||||
|
# Note that memos have to separate processing here. The adds and sends
|
||||||
|
# should be kept to the end because of that, near the emission.
|
||||||
|
clientMsg = copy(lm)
|
||||||
|
serverMsg = copy(lm)
|
||||||
|
|
||||||
|
# Memo-specific processing.
|
||||||
|
if flavor == "memos" and not (is_action or is_ooc):
|
||||||
|
# Quirks were already applied, so get the prefix/postfix stuff
|
||||||
|
# ready.
|
||||||
|
# We fetched the information outside of the loop, so just
|
||||||
|
# construct the messages.
|
||||||
|
|
||||||
|
clientMsg = u"<c={1}>{2}{3}{4}: {0}</c>".format(
|
||||||
|
clientMsg, colorcmd, grammar.pcf, initials, grammar.number
|
||||||
|
)
|
||||||
|
# Not sure if this needs a space at the end...?
|
||||||
|
serverMsg = u"<c={1}>{2}: {0}</c>".format(
|
||||||
|
serverMsg, colorcmd, initials)
|
||||||
|
|
||||||
|
ctx.addMessage(clientMsg, True)
|
||||||
|
if flavor != "menus":
|
||||||
|
# If we're not doing quirk testing, actually send.
|
||||||
|
ctx.messageSent.emit(serverMsg, ctx.title())
|
||||||
|
|
||||||
|
# Clear the input.
|
||||||
|
ctx.textInput.setText("")
|
||||||
|
|
||||||
|
|
||||||
def addTimeInitial(string, grammar):
|
def addTimeInitial(string, grammar):
|
||||||
|
@ -426,6 +845,7 @@ class parseLeaf(object):
|
||||||
out += n
|
out += n
|
||||||
out = self.function(out)
|
out = self.function(out)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
class backreference(object):
|
class backreference(object):
|
||||||
def __init__(self, number):
|
def __init__(self, number):
|
||||||
self.number = number
|
self.number = number
|
||||||
|
|
8
pnc/__init__.py
Normal file
8
pnc/__init__.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# -*- coding=UTF-8; tab-width: 4 -*-
|
||||||
|
|
||||||
|
# These both seem to be worthless because they don't propogate to the modules
|
||||||
|
# lower down....
|
||||||
|
##from __future__ import division
|
||||||
|
##from __future__ import absolute_import # JUST in case.
|
||||||
|
|
||||||
|
# No __all__ or similar, for now.
|
0
pnc/dep/__init__.py
Normal file
0
pnc/dep/__init__.py
Normal file
49
pnc/dep/attrdict.py
Normal file
49
pnc/dep/attrdict.py
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# Modified version of the code featured at the given link
|
||||||
|
## {{{ http://code.activestate.com/recipes/473786/ (r1)
|
||||||
|
class AttrDict(dict):
|
||||||
|
"""A dictionary with attribute-style access. It maps attribute access to
|
||||||
|
the real dictionary."""
|
||||||
|
def __init__(self, init={}): super(AttrDict, self).__init__(init)
|
||||||
|
def __getstate__(self): return self.__dict__.items()
|
||||||
|
def __setstate__(self, items):
|
||||||
|
for key, val in items: self.__dict__[key] = val
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s(%s)" % (
|
||||||
|
type(self).__name__,
|
||||||
|
super(AttrDict, self).__repr__()
|
||||||
|
)
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
return super(AttrDict, self).__setitem__(key, value)
|
||||||
|
def __getitem__(self, name):
|
||||||
|
return super(AttrDict, self).__getitem__(name)
|
||||||
|
def __delitem__(self, name):
|
||||||
|
return super(AttrDict, self).__delitem__(name)
|
||||||
|
__getattr__ = __getitem__
|
||||||
|
__setattr__ = __setitem__
|
||||||
|
__delattr__ = __delitem__
|
||||||
|
def copy(self): return type(self)(self)
|
||||||
|
## end of http://code.activestate.com/recipes/473786/ }}}
|
||||||
|
|
||||||
|
class DefAttrDict(AttrDict):
|
||||||
|
def __init__(self, default_factory=None, *args, **kwargs):
|
||||||
|
self.__dict__["default_factory"] = default_factory
|
||||||
|
super(DefAttrDict, self).__init__(*args, **kwargs)
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s(%r, %s)" % (
|
||||||
|
type(self).__name__,
|
||||||
|
self.default_factory,
|
||||||
|
super(AttrDict, self).__repr__()
|
||||||
|
)
|
||||||
|
def __getitem__(self, name):
|
||||||
|
try:
|
||||||
|
return super(DefAttrDict, self).__getitem__(name)
|
||||||
|
except KeyError:
|
||||||
|
##if self.default_factory is None: return None
|
||||||
|
##return self.default_factory()
|
||||||
|
result = None
|
||||||
|
if self.default_factory is not None:
|
||||||
|
result = self.default_factory()
|
||||||
|
self[name] = result
|
||||||
|
return result
|
||||||
|
__getattr__ = __getitem__
|
||||||
|
def copy(self): return type(self)(self.default_factory, self)
|
579
pnc/lexercon.py
Normal file
579
pnc/lexercon.py
Normal file
|
@ -0,0 +1,579 @@
|
||||||
|
# -*- coding=UTF-8; tab-width: 4 -*-
|
||||||
|
from __future__ import division
|
||||||
|
|
||||||
|
from .unicolor import Color
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
global basestr
|
||||||
|
basestr = str
|
||||||
|
try:
|
||||||
|
basestr = basestring
|
||||||
|
except NameError:
|
||||||
|
# We're running Python 3. Leave it be.
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Yanked from the old Textsub file. Pardon the mess.
|
||||||
|
|
||||||
|
# TODO: Need to consider letting conversions register themselves - or, as a
|
||||||
|
# simpler solution, just have CTag.convert and have it search for a conversion
|
||||||
|
# function appropriate to the given format - e.g. CTag.convert_pchum.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Lexeme(object):
|
||||||
|
def __init__(self, string, origin):
|
||||||
|
self.string = string
|
||||||
|
self.origin = origin
|
||||||
|
def __str__(self):
|
||||||
|
return self.string
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.string)
|
||||||
|
def convert(self, format):
|
||||||
|
##return self.string
|
||||||
|
# This is supposed to be overwritten by subclasses
|
||||||
|
raise NotImplementedError
|
||||||
|
def rebuild(self, format):
|
||||||
|
"""Builds a copy of the owning Lexeme as if it had 'come from' a
|
||||||
|
different original format, and returns the result."""
|
||||||
|
# TODO: This. Need to decide whether overloading will be required for
|
||||||
|
# nearly every single subclass....
|
||||||
|
raise NotImplementedError
|
||||||
|
@classmethod
|
||||||
|
def from_mo(cls, mo, origin):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class Message(Lexeme):
|
||||||
|
"""An object designed to represent a message, possibly containing Lexeme
|
||||||
|
objects in their native form as well. Intended to be a combination of a
|
||||||
|
list and a string, combining the former with the latter's methods."""
|
||||||
|
def __init__(self, contents, origin):
|
||||||
|
lexer = Lexer.lexer_for(origin)()
|
||||||
|
working = lexer.lex(contents)
|
||||||
|
# TODO: Rebuild all lexemes so that they all 'come from' the same
|
||||||
|
# format (e.g. their .origin values are all the same as the Message's).
|
||||||
|
for i, elt in enumerate(working):
|
||||||
|
try:
|
||||||
|
# Try to rebuild for the new format
|
||||||
|
elt = elt.rebuild(origin)
|
||||||
|
except AttributeError:
|
||||||
|
# It doesn't let us rebuild, so it's probably not a Lexeme
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# Assign it to the proper place, replacing the old one
|
||||||
|
working[i] = elt
|
||||||
|
self.origin = origin
|
||||||
|
self.contents = working
|
||||||
|
self.string = ''.join(lexer.list_convert(working))
|
||||||
|
# TODO: Finish all the rest of this.
|
||||||
|
|
||||||
|
|
||||||
|
class Specifier(Lexeme):
|
||||||
|
# Almost purely for classification at present
|
||||||
|
sets_color = sets_bold = sets_italic = sets_underline = None
|
||||||
|
resets_color = resets_bold = resets_italic = resets_underline = None
|
||||||
|
resets_formatting = None
|
||||||
|
|
||||||
|
# Made so that certain odd message-ish things have a place to go. May have its
|
||||||
|
# class changed later.
|
||||||
|
class Chunk(Specifier):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class FTag(Specifier):
|
||||||
|
pass
|
||||||
|
class CTag(Specifier):
|
||||||
|
"""Denotes the beginning or end of a color change."""
|
||||||
|
sets_color = True
|
||||||
|
def __init__(self, string, origin, color):
|
||||||
|
super(CTag, self).__init__(string, origin)
|
||||||
|
# So we can also have None
|
||||||
|
if isinstance(color, tuple):
|
||||||
|
if len(color) < 2: raise ValueError
|
||||||
|
self.color, self.bg_color = color[:2]
|
||||||
|
else:
|
||||||
|
self.color = color
|
||||||
|
self.bg_color = None
|
||||||
|
def has_color(self):
|
||||||
|
if self.color is not None or self.bg_color is not None:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
def convert(self, format):
|
||||||
|
text = ''
|
||||||
|
color = self.color
|
||||||
|
bg = self.bg_color
|
||||||
|
if format == "irc":
|
||||||
|
# Put in the control character for a color code.
|
||||||
|
text = '\x03'
|
||||||
|
if color:
|
||||||
|
text += color.ccode
|
||||||
|
if bg: text += ',' + bg.ccode
|
||||||
|
elif bg: text += "99," + bg.ccode
|
||||||
|
elif format == "pchum":
|
||||||
|
if not color:
|
||||||
|
text = "</c>"
|
||||||
|
else:
|
||||||
|
if color.name:
|
||||||
|
text = "<c=%s>" % color.name
|
||||||
|
else:
|
||||||
|
text = "<c=%d,%d,%d>" % color.to_rgb_tuple()
|
||||||
|
elif format == "plaintext":
|
||||||
|
text = ''
|
||||||
|
return text
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_mo(cls, mo, origin):
|
||||||
|
inst = None
|
||||||
|
if origin == "irc":
|
||||||
|
text = mo.group()
|
||||||
|
fg, bg = mo.groups()
|
||||||
|
try: fg = Color('\x03' + fg)
|
||||||
|
except: fg = None
|
||||||
|
try: bg = Color('\x03' + bg)
|
||||||
|
except: bg = None
|
||||||
|
inst = cls(text, origin, color=(fg, bg))
|
||||||
|
elif origin == "pchum":
|
||||||
|
text = mo.group()
|
||||||
|
inst = cls(text, origin, color=None)
|
||||||
|
if mo.lastindex:
|
||||||
|
text = mo.group(1)
|
||||||
|
cmatch = Pesterchum._ctag_rgb.match(text)
|
||||||
|
if cmatch:
|
||||||
|
working = cmatch.groups()
|
||||||
|
working = map(int, working)
|
||||||
|
inst.color = Color(*working)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
inst.color = Color(text)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return inst
|
||||||
|
class CTagEnd(CTag):
|
||||||
|
# TODO: Make this a separate class - NOT a subclass of CTag like it is at
|
||||||
|
# present
|
||||||
|
resets_color = True
|
||||||
|
def convert(self, format):
|
||||||
|
text = ''
|
||||||
|
if format == "irc": return '\x03'
|
||||||
|
elif format == "pchum": return "</c>"
|
||||||
|
elif format == "plaintext": return ''
|
||||||
|
return text
|
||||||
|
def has_color(self): return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_mo(cls, mo, origin):
|
||||||
|
# Turns the whole match into it (for now)
|
||||||
|
return cls(mo.group(), origin, color=None)
|
||||||
|
class LineColor(CTag):
|
||||||
|
pass
|
||||||
|
class LineColorEnd(CTagEnd):
|
||||||
|
pass
|
||||||
|
class FTagEnd(Specifier):
|
||||||
|
resets_formatting = True
|
||||||
|
class ResetTag(CTagEnd, FTagEnd):
|
||||||
|
def convert(self, format):
|
||||||
|
text = ''
|
||||||
|
if format == "irc": return '\x0F'
|
||||||
|
elif format == "pchum":
|
||||||
|
# Later on, this one is going to be infuriatingly tricky.
|
||||||
|
# Supporting things like bold and so on doesn't really allow for an
|
||||||
|
# easy 'reset' tag.
|
||||||
|
# I *could* implement it, and it wouldn't be too hard, but it would
|
||||||
|
# probably confuse more people than it helped.
|
||||||
|
return "</c>"
|
||||||
|
elif format == "plaintext": return ''
|
||||||
|
return text
|
||||||
|
class SpecifierEnd(CTagEnd, FTagEnd):
|
||||||
|
# This might not ever even be used, but you never know....
|
||||||
|
# If it does, we may need properties such as .resets_color, .resets_bold,
|
||||||
|
# and so on and so forth
|
||||||
|
pass
|
||||||
|
|
||||||
|
# TODO: Consider using a metaclass to check those properties - e.g. if
|
||||||
|
# a class .sets_color and a subclass .resets_color, set the subclass's
|
||||||
|
# .sets_color to False
|
||||||
|
|
||||||
|
|
||||||
|
class Lexer(object):
|
||||||
|
# Subclasses need to supply a ref themselves
|
||||||
|
ref = None
|
||||||
|
def breakdown(self, string, objlist):
|
||||||
|
if not isinstance(string, basestr): msglist = string
|
||||||
|
else: msglist = [string]
|
||||||
|
for obj, rxp in objlist:
|
||||||
|
working = []
|
||||||
|
for i, msg in enumerate(msglist):
|
||||||
|
if not isinstance(msg, basestr):
|
||||||
|
# We've probably got a tag or something else that we can't
|
||||||
|
# actually parse into a tag
|
||||||
|
working.append(msg)
|
||||||
|
continue
|
||||||
|
# If we got here, we have a string to parse
|
||||||
|
oend = 0
|
||||||
|
for mo in rxp.finditer(msg):
|
||||||
|
start, end = mo.span()
|
||||||
|
if oend != start:
|
||||||
|
# There's text between the end of the last match and
|
||||||
|
# the beginning of this one, add it
|
||||||
|
working.append(msg[oend:start])
|
||||||
|
tag = obj.from_mo(mo, origin=self.ref)
|
||||||
|
working.append(tag)
|
||||||
|
oend = end
|
||||||
|
# We've finished parsing every match, check if there's any text
|
||||||
|
# left
|
||||||
|
if oend < len(msg):
|
||||||
|
# There is; add it to the end of the list
|
||||||
|
working.append(msg[oend:])
|
||||||
|
# Exchange the old list with the processed one, and continue
|
||||||
|
msglist = working
|
||||||
|
return msglist
|
||||||
|
def lex(self, string):
|
||||||
|
# Needs to be implemented by subclasses
|
||||||
|
return self.breakdown(string, [])
|
||||||
|
|
||||||
|
def list_convert(self, target, format=None):
|
||||||
|
if format is None: format = self.ref
|
||||||
|
converted = []
|
||||||
|
|
||||||
|
for elt in target:
|
||||||
|
if isinstance(elt, Lexeme):
|
||||||
|
elt = elt.convert(format)
|
||||||
|
if not isinstance(elt, basestr):
|
||||||
|
# Tempted to make this toss an error, but for now, we'll be
|
||||||
|
# safe and make it convert to str
|
||||||
|
elt = str(elt)
|
||||||
|
converted.append(elt)
|
||||||
|
return converted
|
||||||
|
|
||||||
|
class Pesterchum(Lexer):
|
||||||
|
ref = "pchum"
|
||||||
|
_ctag_begin = re.compile(r"<c=(.*?)>", flags=re.I)
|
||||||
|
_ctag_rgb = re.compile(r"(\d+),(\d+),(\d+)")
|
||||||
|
_ctag_end = re.compile(r"</c>", flags=re.I)
|
||||||
|
_mecmdre = re.compile(r"^(/me|PESTERCHUM:ME)(\S*)")
|
||||||
|
|
||||||
|
def lex(self, string):
|
||||||
|
lexlist = [
|
||||||
|
##(mecmd, self._mecmdre),
|
||||||
|
(CTag, self._ctag_begin),
|
||||||
|
##(CTag, self._ctag_end)
|
||||||
|
(CTagEnd, self._ctag_end)
|
||||||
|
]
|
||||||
|
|
||||||
|
lexed = self.breakdown(string, lexlist)
|
||||||
|
|
||||||
|
balanced = []
|
||||||
|
beginc = 0
|
||||||
|
endc = 0
|
||||||
|
for o in lexed:
|
||||||
|
if isinstance(o, CTag):
|
||||||
|
##if o:
|
||||||
|
if o.has_color():
|
||||||
|
# This means it has a color of some sort
|
||||||
|
# TODO: Change this; pesterchum doesn't support BG colors,
|
||||||
|
# so we should only check FG ones (has_color() checks both)
|
||||||
|
# TODO: Consider making a Lexer method that checks if
|
||||||
|
# a provided object would actually contribute something
|
||||||
|
# when rendered under a certain format
|
||||||
|
beginc += 1
|
||||||
|
elif beginc >= endc:
|
||||||
|
endc += 1
|
||||||
|
balanced.append(o)
|
||||||
|
# Original (Pesterchum) code:
|
||||||
|
##if isinstance(o, colorBegin):
|
||||||
|
## beginc += 1
|
||||||
|
## balanced.append(o)
|
||||||
|
##elif isinstance(o, colorEnd):
|
||||||
|
## if beginc >= endc:
|
||||||
|
## endc += 1
|
||||||
|
## balanced.append(o)
|
||||||
|
## else:
|
||||||
|
## balanced.append(o.string)
|
||||||
|
##else:
|
||||||
|
## balanced.append(o)
|
||||||
|
# This will need to be re-evaluated to support the line end lexeme/etc.
|
||||||
|
if beginc > endc:
|
||||||
|
for i in range(0, beginc-endc):
|
||||||
|
##balanced.append(colorEnd("</c>"))
|
||||||
|
balanced.append(CTagEnd("</c>", self.ref, None))
|
||||||
|
return balanced
|
||||||
|
|
||||||
|
def list_convert(self, target, format=None):
|
||||||
|
if format is None: format = self.ref
|
||||||
|
converted = []
|
||||||
|
cstack = []
|
||||||
|
|
||||||
|
##closecolor = lambda: converted.append(CTagEnd("</c>", self.ref, None))
|
||||||
|
closecolor = lambda: converted.append(CTagEnd("</c>", self.ref, None).convert(format))
|
||||||
|
|
||||||
|
for elt in target:
|
||||||
|
if isinstance(elt, LineColorEnd):
|
||||||
|
# Go down the stack until we have a line color TO end
|
||||||
|
while cstack:
|
||||||
|
# Add a </c> since we'll need one anyway
|
||||||
|
closecolor()
|
||||||
|
##if isinstance(color, LineColor):
|
||||||
|
if isinstance(cstack.pop(), LineColor):
|
||||||
|
# We found what we wanted, and the color
|
||||||
|
# was already popped from the stack, so
|
||||||
|
# we're good
|
||||||
|
# Breaking here allows it to be appended
|
||||||
|
break
|
||||||
|
continue
|
||||||
|
elif isinstance(elt, ResetTag):
|
||||||
|
# If it says reset, reset - which means go down the
|
||||||
|
# stack to the most recent line color.
|
||||||
|
while cstack:
|
||||||
|
color = cstack[-1]
|
||||||
|
if not isinstance(color, LineColor):
|
||||||
|
# It's not a line color, so remove it
|
||||||
|
del cstack[-1]
|
||||||
|
# Add a </c>
|
||||||
|
closecolor()
|
||||||
|
else:
|
||||||
|
# It's a line color, so stop searching.
|
||||||
|
# Using break here prevents the 'else'
|
||||||
|
# clause of this while statement from
|
||||||
|
# executing, which means that we go on to
|
||||||
|
# add this to the result.
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# We don't have any more entries in the stack;
|
||||||
|
# just continue.
|
||||||
|
continue
|
||||||
|
## We found the line color, so add it and continue
|
||||||
|
##converted.append(color.convert(format))
|
||||||
|
continue
|
||||||
|
## TODO: Make this actually add the reset char
|
||||||
|
# The above shouldn't be necessary because this is Pesterchum's
|
||||||
|
# format, not IRC's
|
||||||
|
elif isinstance(elt, CTagEnd):
|
||||||
|
try:
|
||||||
|
color = cstack[-1]
|
||||||
|
# Remove the oldest color, the one we're exiting
|
||||||
|
if not isinstance(color, LineColor):
|
||||||
|
# If we got here, we don't have a line color,
|
||||||
|
# so we're free to act as usual
|
||||||
|
cstack.pop()
|
||||||
|
# Fetch the current nested color
|
||||||
|
color = cstack[-1]
|
||||||
|
else:
|
||||||
|
# We have a line color and the current lexeme
|
||||||
|
# is NOT a line color end; don't even bother
|
||||||
|
# adding it to the processed result
|
||||||
|
continue
|
||||||
|
except LookupError:
|
||||||
|
# We aren't nested in a color anymore
|
||||||
|
# Passing here causes us to fall through to normal
|
||||||
|
# handling
|
||||||
|
pass
|
||||||
|
# Not necessary due to Pesterchum's format
|
||||||
|
##else:
|
||||||
|
## # We're still nested....
|
||||||
|
## ##converted.append(elt.convert(format))
|
||||||
|
## converted.append(color.convert(format))
|
||||||
|
## # We already added to the working list, so just
|
||||||
|
## # skip the rest
|
||||||
|
## continue
|
||||||
|
elif isinstance(elt, CTag):
|
||||||
|
# Push the color onto the stack - we're nested in it now
|
||||||
|
cstack.append(elt)
|
||||||
|
# Falling through adds it to the converted result
|
||||||
|
|
||||||
|
if isinstance(elt, Lexeme):
|
||||||
|
elt = elt.convert(format)
|
||||||
|
elif not isinstance(elt, basestr):
|
||||||
|
# Tempted to make this toss an error, but for now, we'll be
|
||||||
|
# safe and make it convert to str
|
||||||
|
elt = str(elt)
|
||||||
|
converted.append(elt)
|
||||||
|
return converted
|
||||||
|
|
||||||
|
class RelayChat(Lexer):
|
||||||
|
ref = "irc"
|
||||||
|
# This could use some cleaning up later, but it'll work for now, hopefully
|
||||||
|
##_ccode_rxp = re.compile(r"\x03(?P<fg>\d\d?)?(?(fg),(?P<bg>\d\d?))?|\x0F")
|
||||||
|
_ccode_rxp = re.compile(r"\x03(?P<fg>\d\d?)(?(fg),(?P<bg>\d\d?))?")
|
||||||
|
_ccode_end_rxp = re.compile(r"\x03(?!\d\d?)")
|
||||||
|
_reset_rxp = re.compile(r"\x0F")
|
||||||
|
|
||||||
|
def lex(self, string):
|
||||||
|
##lexlist = [(CTag, self._ccode_rxp)]
|
||||||
|
lexlist = [
|
||||||
|
(CTag, self._ccode_rxp),
|
||||||
|
(CTagEnd, self._ccode_end_rxp),
|
||||||
|
(ResetTag, self._reset_rxp)
|
||||||
|
]
|
||||||
|
|
||||||
|
lexed = self.breakdown(string, lexlist)
|
||||||
|
|
||||||
|
# Don't bother with the whole fancy color-balancing thing yet
|
||||||
|
return lexed
|
||||||
|
|
||||||
|
def list_convert(self, target, format=None):
|
||||||
|
if format is None: format = self.ref
|
||||||
|
converted = []
|
||||||
|
cstack = []
|
||||||
|
|
||||||
|
for elt in target:
|
||||||
|
if isinstance(elt, CTag):
|
||||||
|
if isinstance(elt, CTagEnd) or not elt.has_color():
|
||||||
|
if isinstance(elt, LineColorEnd):
|
||||||
|
# Go down the stack until we have a line color TO
|
||||||
|
# end
|
||||||
|
while cstack:
|
||||||
|
##if isinstance(color, LineColor):
|
||||||
|
if isinstance(cstack.pop(), LineColor):
|
||||||
|
# We found what we wanted, and the color
|
||||||
|
# was already popped from the stack, so
|
||||||
|
# we're good
|
||||||
|
break
|
||||||
|
# The current lexeme isn't a line color end
|
||||||
|
elif isinstance(elt, ResetTag):
|
||||||
|
# If it says reset, reset - which means go down the
|
||||||
|
# stack to the most recent line color.
|
||||||
|
while cstack:
|
||||||
|
color = cstack[-1]
|
||||||
|
if not isinstance(color, LineColor):
|
||||||
|
# It's not a line color, so remove it
|
||||||
|
del cstack[-1]
|
||||||
|
else:
|
||||||
|
# It's a line color, so stop searching.
|
||||||
|
# Using break here prevents the 'else'
|
||||||
|
# clause of this while statement from
|
||||||
|
# executing.
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# We don't have any more entries in the stack;
|
||||||
|
# just continue.
|
||||||
|
continue
|
||||||
|
# We found the line color, so add it and continue
|
||||||
|
converted.append(color.convert(format))
|
||||||
|
continue
|
||||||
|
# TODO: Make this actually add the reset char
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
color = cstack[-1]
|
||||||
|
# Remove the oldest color, the one we're exiting
|
||||||
|
if not isinstance(color, LineColor):
|
||||||
|
# If we got here, we don't have a line color,
|
||||||
|
# so we're free to act as usual
|
||||||
|
cstack.pop()
|
||||||
|
# Fetch the current nested color
|
||||||
|
color = cstack[-1]
|
||||||
|
else:
|
||||||
|
# We have a line color and the current lexeme
|
||||||
|
# is NOT a line color end; don't even bother
|
||||||
|
# adding it to the processed result
|
||||||
|
continue
|
||||||
|
except LookupError:
|
||||||
|
# We aren't nested in a color anymore
|
||||||
|
# Passing here causes us to fall through to normal
|
||||||
|
# handling
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# We're still nested....
|
||||||
|
##converted.append(elt.convert(format))
|
||||||
|
converted.append(color.convert(format))
|
||||||
|
# We already added to the working list, so just
|
||||||
|
# skip the rest
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# Push the color onto the stack - we're nested in it now
|
||||||
|
cstack.append(elt)
|
||||||
|
|
||||||
|
if isinstance(elt, Lexeme):
|
||||||
|
elt = elt.convert(format)
|
||||||
|
elif not isinstance(elt, basestr):
|
||||||
|
# Tempted to make this toss an error, but for now, we'll be
|
||||||
|
# safe and make it convert to str
|
||||||
|
elt = str(elt)
|
||||||
|
converted.append(elt)
|
||||||
|
return converted
|
||||||
|
|
||||||
|
def _list_convert_new(self, target, format=None):
|
||||||
|
if format is None: format = self.ref
|
||||||
|
converted = []
|
||||||
|
cstack = []
|
||||||
|
|
||||||
|
for elt in target:
|
||||||
|
if isinstance(elt, LineColorEnd):
|
||||||
|
# Go down the stack until we have a line color TO end
|
||||||
|
while cstack:
|
||||||
|
# Add a </c> since we'll need one anyway
|
||||||
|
closecolor()
|
||||||
|
##if isinstance(color, LineColor):
|
||||||
|
if isinstance(cstack.pop(), LineColor):
|
||||||
|
# We found what we wanted, and the color
|
||||||
|
# was already popped from the stack, so
|
||||||
|
# we're good
|
||||||
|
# Breaking here allows it to be appended
|
||||||
|
break
|
||||||
|
continue
|
||||||
|
elif isinstance(elt, ResetTag):
|
||||||
|
# If it says reset, reset - which means go down the
|
||||||
|
# stack to the most recent line color.
|
||||||
|
while cstack:
|
||||||
|
color = cstack[-1]
|
||||||
|
if not isinstance(color, LineColor):
|
||||||
|
# It's not a line color, so remove it
|
||||||
|
del cstack[-1]
|
||||||
|
# Add a </c>
|
||||||
|
closecolor()
|
||||||
|
else:
|
||||||
|
# It's a line color, so stop searching.
|
||||||
|
# Using break here prevents the 'else'
|
||||||
|
# clause of this while statement from
|
||||||
|
# executing.
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# We don't have any more entries in the stack;
|
||||||
|
# just continue.
|
||||||
|
continue
|
||||||
|
## We found the line color, so add it and continue
|
||||||
|
##converted.append(color.convert(format))
|
||||||
|
continue
|
||||||
|
## TODO: Make this actually add the reset char
|
||||||
|
# The above shouldn't be necessary because this is Pesterchum's
|
||||||
|
# format, not IRC's
|
||||||
|
elif isinstance(elt, CTagEnd):
|
||||||
|
try:
|
||||||
|
color = cstack[-1]
|
||||||
|
# Remove the oldest color, the one we're exiting
|
||||||
|
if not isinstance(color, LineColor):
|
||||||
|
# If we got here, we don't have a line color,
|
||||||
|
# so we're free to act as usual
|
||||||
|
cstack.pop()
|
||||||
|
# Fetch the current nested color
|
||||||
|
color = cstack[-1]
|
||||||
|
else:
|
||||||
|
# We have a line color and the current lexeme
|
||||||
|
# is NOT a line color end; don't even bother
|
||||||
|
# adding it to the processed result
|
||||||
|
continue
|
||||||
|
except LookupError:
|
||||||
|
# We aren't nested in a color anymore
|
||||||
|
# Passing here causes us to fall through to normal
|
||||||
|
# handling
|
||||||
|
pass
|
||||||
|
# Not necessary due to Pesterchum's format
|
||||||
|
##else:
|
||||||
|
## # We're still nested....
|
||||||
|
## ##converted.append(elt.convert(format))
|
||||||
|
## converted.append(color.convert(format))
|
||||||
|
## # We already added to the working list, so just
|
||||||
|
## # skip the rest
|
||||||
|
## continue
|
||||||
|
elif isinstance(elt, CTag):
|
||||||
|
# Push the color onto the stack - we're nested in it now
|
||||||
|
cstack.append(elt)
|
||||||
|
# Falling through adds it to the converted result
|
||||||
|
|
||||||
|
if isinstance(elt, Lexeme):
|
||||||
|
elt = elt.convert(format)
|
||||||
|
elif not isinstance(elt, basestr):
|
||||||
|
# Tempted to make this toss an error, but for now, we'll be
|
||||||
|
# safe and make it convert to str
|
||||||
|
elt = str(elt)
|
||||||
|
converted.append(elt)
|
||||||
|
return converted
|
601
pnc/unicolor.py
Normal file
601
pnc/unicolor.py
Normal file
|
@ -0,0 +1,601 @@
|
||||||
|
# -*- coding=UTF-8; tab-width: 4 -*-
|
||||||
|
from __future__ import division
|
||||||
|
|
||||||
|
__all__ = ["Color"]
|
||||||
|
|
||||||
|
# Copied from my old Textsub script. Please forgive the mess, and keep in mind
|
||||||
|
# that this may be phased out in the future.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from .dep.attrdict import AttrDict
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import functools
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Python 3 checking
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
|
basestr = basestring
|
||||||
|
else:
|
||||||
|
basestr = str
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# A named tuple for containing CIE L*a*b* (CIELAB) information.
|
||||||
|
# NOTE TO THOSE MAINTAINING: If you don't know what that means, you're going to
|
||||||
|
# hate yourself *and* me if you try to edit this. I know I did when I wrote it.
|
||||||
|
LabTuple = collections.namedtuple("LabTuple", ['L', 'a', 'b'])
|
||||||
|
class Color(object):
|
||||||
|
# The threshold at which to consider two colors noticeably different, even
|
||||||
|
# if only barely
|
||||||
|
jnd = 2.3
|
||||||
|
# TODO: Either subclass (this is probably best) or add a .native_type; in
|
||||||
|
# the case of the former, just make sure each type is geared towards using
|
||||||
|
# a certain kind of color space as a starting point, e.g. RGB, XYZ, HSV,
|
||||||
|
# CIELAB, etc...
|
||||||
|
# TODO: color_for_name()
|
||||||
|
# TODO: Split __init__, partly using __new__, so the former just has to do
|
||||||
|
# conversions
|
||||||
|
##def __new__(cls, *args, **kwargs):
|
||||||
|
## nargs = len(args)
|
||||||
|
## if nargs > 0: arg = args[0]
|
||||||
|
## if (nargs == 1
|
||||||
|
## and isinstance(arg, basestr) and not arg.startswith('#')
|
||||||
|
## ):
|
||||||
|
## # Try to look up the color name
|
||||||
|
## name = arg.lower()
|
||||||
|
## try:
|
||||||
|
## color = _svg_colors[name]
|
||||||
|
## except LookupError:
|
||||||
|
## # We don't have a color with that name
|
||||||
|
## raise ValueError("No color with name '%s' found" % name)
|
||||||
|
## else:
|
||||||
|
## # Hand over a copy of the color we found
|
||||||
|
## return cls(color)
|
||||||
|
## else:
|
||||||
|
## return super(Color, cls).__new__(cls)
|
||||||
|
## inst = super(Color, cls).__new__(cls)
|
||||||
|
## inst.ccode = ''
|
||||||
|
## nargs = len(args)
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.ccode = ''
|
||||||
|
self.closest_name = self.name = None
|
||||||
|
nargs = len(args)
|
||||||
|
if nargs == 1:
|
||||||
|
# Make this a little easier to type out by reducing what we need to
|
||||||
|
# work with
|
||||||
|
arg = args[0]
|
||||||
|
if isinstance(arg, int):
|
||||||
|
# Assume we were passed a raw hexadecimal value
|
||||||
|
# Again, handle this the easy way
|
||||||
|
arg = "#%06X" % arg
|
||||||
|
# Using 'if' instead of 'elif' here allows us to fall through from
|
||||||
|
# the above, which is, of course, quite useful in this situation
|
||||||
|
if isinstance(arg, basestr):
|
||||||
|
# If it's a string, we've probably got a hex code, but check
|
||||||
|
# anyway just in case
|
||||||
|
if arg.startswith('#'):
|
||||||
|
self.hexstr = self.sanitize_hex(arg)
|
||||||
|
rgb = self.hexstr_to_rgb(self.hexstr)
|
||||||
|
self.red, self.green, self.blue = rgb
|
||||||
|
##return
|
||||||
|
# TODO: This.
|
||||||
|
elif (arg.startswith('\003') and len(arg) > 1
|
||||||
|
or len(arg) < 3 and arg.isdigit()):
|
||||||
|
# We have an IRC-style color code
|
||||||
|
arg = arg.lstrip('\003')
|
||||||
|
# Just in case
|
||||||
|
arg = arg.split(',')[0]
|
||||||
|
cnum = int(arg)
|
||||||
|
try: color = _irc_colors[cnum]
|
||||||
|
except LookupError:
|
||||||
|
raise ValueError("No color for ccode %r found" % cnum)
|
||||||
|
# We found a color; fall through and so on
|
||||||
|
arg = color
|
||||||
|
else:
|
||||||
|
# Presumably we have a color name
|
||||||
|
name = arg.lower()
|
||||||
|
try: color = _svg_colors[name]
|
||||||
|
except LookupError:
|
||||||
|
raise ValueError("No color with name %r found" % name)
|
||||||
|
# We found a color; fall through so we make this one a copy
|
||||||
|
arg = color
|
||||||
|
if isinstance(arg, Color):
|
||||||
|
# We were passed a Color object - just duplicate it.
|
||||||
|
# For now, we'll do things the cheap way....
|
||||||
|
self.red, self.green, self.blue = arg.to_rgb_tuple()
|
||||||
|
self.hexstr = arg.hexstr
|
||||||
|
self.closest_name = arg.closest_name
|
||||||
|
self.name = arg.name
|
||||||
|
self.ccode = arg.ccode
|
||||||
|
elif nargs == 3:
|
||||||
|
# Assume we've got RGB
|
||||||
|
# Map to abs() so we can't get messed up results due to negatives
|
||||||
|
args = list(map(abs, args))
|
||||||
|
self.red, self.green, self.blue = args
|
||||||
|
# Convert for the hex code
|
||||||
|
self.hexstr = self.rgb_to_hexstr(*args)
|
||||||
|
##return
|
||||||
|
else:
|
||||||
|
# We don't know how to handle the value we recieved....
|
||||||
|
raise ValueError
|
||||||
|
# Otherwise, calculate XYZ for this
|
||||||
|
self.x, self.y, self.z = self.rgb_to_xyz(*self.to_rgb_tuple())
|
||||||
|
# Calculate the LAB color
|
||||||
|
self.cielab = LabTuple(*self.xyz_to_cielab(*self.to_xyz_tuple()))
|
||||||
|
if not self.closest_name: self.closest_name = self.get_svg_name()
|
||||||
|
if not self.ccode: self.ccode = self.get_ccode()
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return hash(self) == hash(other)
|
||||||
|
def __ne__(self, other): return not self.__eq__(other)
|
||||||
|
def __sub__(self, other):
|
||||||
|
if not isinstance(other, Color): raise TypeError
|
||||||
|
return self.distance(other)
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
##result = map(hash, [
|
||||||
|
## str(self).upper(),
|
||||||
|
## self.red, self.green, self.blue
|
||||||
|
## ])
|
||||||
|
# 2012-12-08T13:34-07:00: This should improve accuracy
|
||||||
|
result = map(hash, self.cielab)
|
||||||
|
result = functools.reduce(lambda x, y: x ^ y, result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Before the change on 2012-12-08, the above was equivalent to the old
|
||||||
|
# code, which was this:
|
||||||
|
##result = hash(str(self).upper())
|
||||||
|
##result ^= self.red
|
||||||
|
##result ^= self.green
|
||||||
|
##result ^= self.blue
|
||||||
|
##return result
|
||||||
|
def __repr__(self):
|
||||||
|
##return "%s(%r)" % (type(self).__name__, str(self))
|
||||||
|
return "%s(%r)" % (type(self).__name__,
|
||||||
|
self.reduce_hexstr(self.hexstr))
|
||||||
|
def __str__(self):
|
||||||
|
##return self.reduce_hexstr(self.hexstr)
|
||||||
|
return self.name()
|
||||||
|
|
||||||
|
# Builtins
|
||||||
|
# These were yanked from Hostmask and changed around a bit
|
||||||
|
def __getitem__(self, ind): return (self.red, self.green, self.blue)[ind]
|
||||||
|
def __iter__(self):
|
||||||
|
targs = (self.red, self.green, self.blue)
|
||||||
|
for t in targs:
|
||||||
|
yield t
|
||||||
|
# If we got here, we're out of attributes to provide
|
||||||
|
raise StopIteration
|
||||||
|
##def __len__(self):
|
||||||
|
## # Acceptable so long as we're returning RGB like we currently (at TOW)
|
||||||
|
## # are
|
||||||
|
## return 3
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_ccode(cls, ccode):
|
||||||
|
if isinstance(ccode, basestr):
|
||||||
|
# We were passed a string
|
||||||
|
ccode = ccode.lstrip('\003')
|
||||||
|
ccode = ccode.split(',')
|
||||||
|
if len(ccode) < 2:
|
||||||
|
fg = ccode[0]
|
||||||
|
bg = None
|
||||||
|
else:
|
||||||
|
fg, bg = ccode
|
||||||
|
try:
|
||||||
|
fg = int(fg)
|
||||||
|
except ValueError:
|
||||||
|
# We started with a string to the effect of ",00"
|
||||||
|
fg = -1
|
||||||
|
try:
|
||||||
|
bg = int(bg)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
# We started with a string to the effect of "00,", or it didn't
|
||||||
|
# have a comma
|
||||||
|
bg = -1
|
||||||
|
else:
|
||||||
|
fg = ccode
|
||||||
|
bg = -1
|
||||||
|
|
||||||
|
try:
|
||||||
|
fg = _irc_colors[fg]
|
||||||
|
except LookupError:
|
||||||
|
# We had a string to the effect of ",00", or the color code
|
||||||
|
# couldn't be found
|
||||||
|
# TODO: Consider making a ValueError return a different value?
|
||||||
|
fg = None
|
||||||
|
else:
|
||||||
|
fg = Color(fg)
|
||||||
|
try:
|
||||||
|
bg = _irc_colors[bg]
|
||||||
|
except LookupError:
|
||||||
|
# See above note.
|
||||||
|
bg = None
|
||||||
|
else:
|
||||||
|
bg = Color(bg)
|
||||||
|
##if bg: return fg, bg
|
||||||
|
return fg, bg
|
||||||
|
|
||||||
|
def get_ccode(self):
|
||||||
|
closest, cldist = None, None
|
||||||
|
targs = _irc_colors
|
||||||
|
|
||||||
|
for code, other in targs.items():
|
||||||
|
dist = self - other
|
||||||
|
##if (not strict and dist > self.jnd) or dist == 0:
|
||||||
|
if dist == 0:
|
||||||
|
# We have a perfect match!
|
||||||
|
# Just return the relevant color code right now
|
||||||
|
return "%02d" % code
|
||||||
|
if cldist is None or cldist > dist:
|
||||||
|
closest, cldist = "%02d" % code, dist
|
||||||
|
# We've found the closest matching color code; return it
|
||||||
|
return closest
|
||||||
|
|
||||||
|
def get_svg_name(self, strict=False):
|
||||||
|
closest, cldist = None, None
|
||||||
|
targs = _svg_colors
|
||||||
|
|
||||||
|
for name, other in targs.items():
|
||||||
|
dist = self - other
|
||||||
|
if (not strict and dist > self.jnd) or dist == 0:
|
||||||
|
# The difference is below the Just-Noticeable Difference
|
||||||
|
# threshold, or we have a perfect match; consider them roughly
|
||||||
|
# the same
|
||||||
|
return name
|
||||||
|
if cldist is None or cldist > dist:
|
||||||
|
closest, cldist = name, dist
|
||||||
|
# We've found the closest matching color name; return it
|
||||||
|
return closest
|
||||||
|
|
||||||
|
##def name(self): return self.closest_name
|
||||||
|
|
||||||
|
def distance(self, other):
|
||||||
|
# CIELAB distance, adapted from distance() and checked vs. Wikipedia:
|
||||||
|
# http://en.wikipedia.org/wiki/Color_difference
|
||||||
|
slab, olab = self.to_cielab_tuple(), other.to_cielab_tuple()
|
||||||
|
# Calculate the distance between the points for each
|
||||||
|
dist = map(lambda p1, p2: (p2 - p1)**2, slab, olab)
|
||||||
|
# Add the results up, and sqrt to compensate for earlier squaring
|
||||||
|
dist = sum(dist) ** .5
|
||||||
|
return dist
|
||||||
|
|
||||||
|
def rgb_distance(self, other):
|
||||||
|
# The older version of distance().
|
||||||
|
##r1, r2 = self.red, other.red
|
||||||
|
##g1, g2 = self.green, other.green
|
||||||
|
##b1, b2 = self.blue, other.blue
|
||||||
|
srgb, orgb = self.to_rgb_tuple(), other.to_rgb_tuple()
|
||||||
|
### Subtract the RGBs from each other (i.e. r1 - r2, g1 - g2, b1 - b2)
|
||||||
|
##dist = map(operator.sub, srgb, orgb)
|
||||||
|
### Square the results from the above
|
||||||
|
##dist = [x**2 for x in dist]
|
||||||
|
# Do what we WOULD have done in those two lines with a single one
|
||||||
|
dist = map(lambda x1, x2: (x1 - x2)**2, srgb, orgb)
|
||||||
|
# Add the results up
|
||||||
|
dist = sum(dist)
|
||||||
|
# Fetch the square root to compensate for the earlier squaring
|
||||||
|
dist **= .5
|
||||||
|
return dist
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def hexstr_to_rgb(cls, hexstr):
|
||||||
|
hexstr = cls.sanitize_hex(hexstr)
|
||||||
|
hexstr = hexstr.lstrip('#')
|
||||||
|
# This is ugly, but the purpose is simple and it's accomplished in a
|
||||||
|
# single line...it just runs through the string, picking two characters
|
||||||
|
# at a time and converting them from hex values to ints.
|
||||||
|
result = tuple(int(hexstr[i:i+2], 16) for i in range(len(hexstr))[::2])
|
||||||
|
return result
|
||||||
|
##working = collections.deque(hexstr)
|
||||||
|
##result = []
|
||||||
|
### The alternative to doing it this way would be to use an int-driven
|
||||||
|
### 'for' loop, or similar which might be preferable
|
||||||
|
##while working:
|
||||||
|
## # Fetch the next two args and convert them to an int
|
||||||
|
## i = int(working.popleft() + working.popleft(), 16)
|
||||||
|
## result.append(i)
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def rgb_to_hexstr(red, green, blue):
|
||||||
|
rgb = [red, green, blue]
|
||||||
|
rgb = map(abs, rgb)
|
||||||
|
# Preemptively add the '#' to our result
|
||||||
|
result = ['#']
|
||||||
|
for c in rgb:
|
||||||
|
### Convert to hex, stripping the leading "0x" that Python adds
|
||||||
|
##c = hex(c).lstrip("0x", 1)
|
||||||
|
### Add a '0' in front if it's just a single digit
|
||||||
|
##c = ('0' + c)[-2:]
|
||||||
|
c = "%02X" % c
|
||||||
|
# Append to our result
|
||||||
|
result.append(c)
|
||||||
|
# Join and return the result
|
||||||
|
return ''.join(result)
|
||||||
|
|
||||||
|
# These next two are from http://www.easyrgb.com/index.php?X=MATH
|
||||||
|
@staticmethod
|
||||||
|
def rgb_to_xyz(red, green, blue):
|
||||||
|
rgb = [red, green, blue]
|
||||||
|
for i, n in enumerate(rgb):
|
||||||
|
n /= 255
|
||||||
|
if n > 0.04045: n = ( ( n + 0.055 ) / 1.055 ) ** 2.4
|
||||||
|
else: n /= 12.92
|
||||||
|
rgb[i] = n * 100
|
||||||
|
r, g, b = rgb
|
||||||
|
x = r * 0.4124 + g * 0.3576 + b * 0.1805
|
||||||
|
y = r * 0.2126 + g * 0.7152 + b * 0.0722
|
||||||
|
z = r * 0.0193 + g * 0.1192 + b * 0.9505
|
||||||
|
##x = 0.436052025 * r + 0.385081593 * g + 0.143087414 * b
|
||||||
|
##y = 0.222491598 * r + 0.71688606 * g + 0.060621486 * b
|
||||||
|
##z = 0.013929122 * r + 0.097097002 * g + 0.71418547 * b
|
||||||
|
return x, y, z
|
||||||
|
@staticmethod
|
||||||
|
def xyz_to_cielab(x, y, z):
|
||||||
|
# Reference X, Y, and Z
|
||||||
|
refs = [95.047, 100.000, 108.883]
|
||||||
|
ref_x, ref_y, ref_z = refs
|
||||||
|
##xyz = [x / ref_x, y / ref_y, z / ref_z]
|
||||||
|
xyz = [x, y, z]
|
||||||
|
for i, n in enumerate(xyz):
|
||||||
|
n /= refs[i]
|
||||||
|
if n > 0.008856: n **= 1/3
|
||||||
|
else:
|
||||||
|
n *= 7.787
|
||||||
|
n += 16/116
|
||||||
|
xyz[i] = n
|
||||||
|
x, y, z = xyz
|
||||||
|
l = (y*116) - 16
|
||||||
|
a = (x - y) * 500
|
||||||
|
b = (y - z) * 200
|
||||||
|
return l, a, b
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def reduce_hexstr(hexstr):
|
||||||
|
orig = hexstr
|
||||||
|
hexstr = hexstr.lstrip('#')
|
||||||
|
lhexstr = hexstr.lower()
|
||||||
|
strlen = len(hexstr)
|
||||||
|
working = ['#']
|
||||||
|
for i in range(strlen)[::2]:
|
||||||
|
if lhexstr[i] == lhexstr[i+1]:
|
||||||
|
# The two characters fetched are the same, so we can reduce
|
||||||
|
working.append(hexstr[i])
|
||||||
|
else:
|
||||||
|
# The two characters differ, so we can't actually reduce this
|
||||||
|
# string at all; just return the original
|
||||||
|
return orig
|
||||||
|
# If we got here, we successfully reduced
|
||||||
|
return ''.join(working)
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sanitize_hex(hexstr):
|
||||||
|
hexstr = hexstr.upper()
|
||||||
|
# We don't need the leading hash mark for now
|
||||||
|
hexstr = hexstr.lstrip('#')
|
||||||
|
strlen = len(hexstr)
|
||||||
|
if strlen == 6:
|
||||||
|
return '#' + hexstr
|
||||||
|
elif strlen == 3:
|
||||||
|
# We have a short (CSS style) code; duplicate all of the characters
|
||||||
|
hexstr = [c + c for c in hexstr]
|
||||||
|
hexstr = ''.join(hexstr)
|
||||||
|
return '#' + hexstr
|
||||||
|
# Should probably error out, but that can wait until later
|
||||||
|
return '#' + hexstr
|
||||||
|
|
||||||
|
def to_cielab_tuple(self):
|
||||||
|
# For now, just return the stored CIELAB tuple
|
||||||
|
return self.cielab
|
||||||
|
def to_rgb_tuple(self): return (self.red, self.green, self.blue)
|
||||||
|
# 2012-12-05T17:40:39-07:00: Changed 'self.blue' to 'self.z' like it SHOULD
|
||||||
|
# have been in the FIRST place. Ugh. How did I fuck THAT one up?
|
||||||
|
def to_xyz_tuple(self): return (self.x, self.y, self.z)
|
||||||
|
|
||||||
|
# All of these are effectively equivalent to the Qt-provided colors, so they
|
||||||
|
# could be phased out - but there's no need to, yet.
|
||||||
|
_svg_colors = AttrDict()
|
||||||
|
_irc_colors = {}
|
||||||
|
_svg_colors.update({
|
||||||
|
"aliceblue": Color(240, 248, 255),
|
||||||
|
"antiquewhite": Color(250, 235, 215),
|
||||||
|
"aqua": Color( 0, 255, 255),
|
||||||
|
"aquamarine": Color(127, 255, 212),
|
||||||
|
"azure": Color(240, 255, 255),
|
||||||
|
"beige": Color(245, 245, 220),
|
||||||
|
"bisque": Color(255, 228, 196),
|
||||||
|
"black": Color( 0, 0, 0),
|
||||||
|
"blanchedalmond": Color(255, 235, 205),
|
||||||
|
"blue": Color( 0, 0, 255),
|
||||||
|
"blueviolet": Color(138, 43, 226),
|
||||||
|
"brown": Color(165, 42, 42),
|
||||||
|
"burlywood": Color(222, 184, 135),
|
||||||
|
"cadetblue": Color( 95, 158, 160),
|
||||||
|
"chartreuse": Color(127, 255, 0),
|
||||||
|
"chocolate": Color(210, 105, 30),
|
||||||
|
"coral": Color(255, 127, 80),
|
||||||
|
"cornflowerblue": Color(100, 149, 237),
|
||||||
|
"cornsilk": Color(255, 248, 220),
|
||||||
|
"crimson": Color(220, 20, 60),
|
||||||
|
"cyan": Color( 0, 255, 255),
|
||||||
|
"darkblue": Color( 0, 0, 139),
|
||||||
|
"darkcyan": Color( 0, 139, 139),
|
||||||
|
"darkgoldenrod": Color(184, 134, 11),
|
||||||
|
"darkgray": Color(169, 169, 169),
|
||||||
|
"darkgreen": Color( 0, 100, 0),
|
||||||
|
"darkgrey": Color(169, 169, 169),
|
||||||
|
"darkkhaki": Color(189, 183, 107),
|
||||||
|
"darkmagenta": Color(139, 0, 139),
|
||||||
|
"darkolivegreen": Color( 85, 107, 47),
|
||||||
|
"darkorange": Color(255, 140, 0),
|
||||||
|
"darkorchid": Color(153, 50, 204),
|
||||||
|
"darkred": Color(139, 0, 0),
|
||||||
|
"darksalmon": Color(233, 150, 122),
|
||||||
|
"darkseagreen": Color(143, 188, 143),
|
||||||
|
"darkslateblue": Color( 72, 61, 139),
|
||||||
|
"darkslategray": Color( 47, 79, 79),
|
||||||
|
"darkslategrey": Color( 47, 79, 79),
|
||||||
|
"darkturquoise": Color( 0, 206, 209),
|
||||||
|
"darkviolet": Color(148, 0, 211),
|
||||||
|
"deeppink": Color(255, 20, 147),
|
||||||
|
"deepskyblue": Color( 0, 191, 255),
|
||||||
|
"dimgray": Color(105, 105, 105),
|
||||||
|
"dimgrey": Color(105, 105, 105),
|
||||||
|
"dodgerblue": Color( 30, 144, 255),
|
||||||
|
"firebrick": Color(178, 34, 34),
|
||||||
|
"floralwhite": Color(255, 250, 240),
|
||||||
|
"forestgreen": Color( 34, 139, 34),
|
||||||
|
"fuchsia": Color(255, 0, 255),
|
||||||
|
"gainsboro": Color(220, 220, 220),
|
||||||
|
"ghostwhite": Color(248, 248, 255),
|
||||||
|
"gold": Color(255, 215, 0),
|
||||||
|
"goldenrod": Color(218, 165, 32),
|
||||||
|
"gray": Color(128, 128, 128),
|
||||||
|
"grey": Color(128, 128, 128),
|
||||||
|
"green": Color( 0, 128, 0),
|
||||||
|
"greenyellow": Color(173, 255, 47),
|
||||||
|
"honeydew": Color(240, 255, 240),
|
||||||
|
"hotpink": Color(255, 105, 180),
|
||||||
|
"indianred": Color(205, 92, 92),
|
||||||
|
"indigo": Color( 75, 0, 130),
|
||||||
|
"ivory": Color(255, 255, 240),
|
||||||
|
"khaki": Color(240, 230, 140),
|
||||||
|
"lavender": Color(230, 230, 250),
|
||||||
|
"lavenderblush": Color(255, 240, 245),
|
||||||
|
"lawngreen": Color(124, 252, 0),
|
||||||
|
"lemonchiffon": Color(255, 250, 205),
|
||||||
|
"lightblue": Color(173, 216, 230),
|
||||||
|
"lightcoral": Color(240, 128, 128),
|
||||||
|
"lightcyan": Color(224, 255, 255),
|
||||||
|
"lightgoldenrodyellow": Color(250, 250, 210),
|
||||||
|
"lightgray": Color(211, 211, 211),
|
||||||
|
"lightgreen": Color(144, 238, 144),
|
||||||
|
"lightgrey": Color(211, 211, 211),
|
||||||
|
"lightpink": Color(255, 182, 193),
|
||||||
|
"lightsalmon": Color(255, 160, 122),
|
||||||
|
"lightseagreen": Color( 32, 178, 170),
|
||||||
|
"lightskyblue": Color(135, 206, 250),
|
||||||
|
"lightslategray": Color(119, 136, 153),
|
||||||
|
"lightslategrey": Color(119, 136, 153),
|
||||||
|
"lightsteelblue": Color(176, 196, 222),
|
||||||
|
"lightyellow": Color(255, 255, 224),
|
||||||
|
"lime": Color( 0, 255, 0),
|
||||||
|
"limegreen": Color( 50, 205, 50),
|
||||||
|
"linen": Color(250, 240, 230),
|
||||||
|
"magenta": Color(255, 0, 255),
|
||||||
|
"maroon": Color(128, 0, 0),
|
||||||
|
"mediumaquamarine": Color(102, 205, 170),
|
||||||
|
"mediumblue": Color( 0, 0, 205),
|
||||||
|
"mediumorchid": Color(186, 85, 211),
|
||||||
|
"mediumpurple": Color(147, 112, 219),
|
||||||
|
"mediumseagreen": Color( 60, 179, 113),
|
||||||
|
"mediumslateblue": Color(123, 104, 238),
|
||||||
|
"mediumspringgreen": Color( 0, 250, 154),
|
||||||
|
"mediumturquoise": Color( 72, 209, 204),
|
||||||
|
"mediumvioletred": Color(199, 21, 133),
|
||||||
|
"midnightblue": Color( 25, 25, 112),
|
||||||
|
"mintcream": Color(245, 255, 250),
|
||||||
|
"mistyrose": Color(255, 228, 225),
|
||||||
|
"moccasin": Color(255, 228, 181),
|
||||||
|
"navajowhite": Color(255, 222, 173),
|
||||||
|
"navy": Color( 0, 0, 128),
|
||||||
|
"oldlace": Color(253, 245, 230),
|
||||||
|
"olive": Color(128, 128, 0),
|
||||||
|
"olivedrab": Color(107, 142, 35),
|
||||||
|
"orange": Color(255, 165, 0),
|
||||||
|
"orangered": Color(255, 69, 0),
|
||||||
|
"orchid": Color(218, 112, 214),
|
||||||
|
"palegoldenrod": Color(238, 232, 170),
|
||||||
|
"palegreen": Color(152, 251, 152),
|
||||||
|
"paleturquoise": Color(175, 238, 238),
|
||||||
|
"palevioletred": Color(219, 112, 147),
|
||||||
|
"papayawhip": Color(255, 239, 213),
|
||||||
|
"peachpuff": Color(255, 218, 185),
|
||||||
|
"peru": Color(205, 133, 63),
|
||||||
|
"pink": Color(255, 192, 203),
|
||||||
|
"plum": Color(221, 160, 221),
|
||||||
|
"powderblue": Color(176, 224, 230),
|
||||||
|
"purple": Color(128, 0, 128),
|
||||||
|
"red": Color(255, 0, 0),
|
||||||
|
"rosybrown": Color(188, 143, 143),
|
||||||
|
"royalblue": Color( 65, 105, 225),
|
||||||
|
"saddlebrown": Color(139, 69, 19),
|
||||||
|
"salmon": Color(250, 128, 114),
|
||||||
|
"sandybrown": Color(244, 164, 96),
|
||||||
|
"seagreen": Color( 46, 139, 87),
|
||||||
|
"seashell": Color(255, 245, 238),
|
||||||
|
"sienna": Color(160, 82, 45),
|
||||||
|
"silver": Color(192, 192, 192),
|
||||||
|
"skyblue": Color(135, 206, 235),
|
||||||
|
"slateblue": Color(106, 90, 205),
|
||||||
|
"slategray": Color(112, 128, 144),
|
||||||
|
"slategrey": Color(112, 128, 144),
|
||||||
|
"snow": Color(255, 250, 250),
|
||||||
|
"springgreen": Color( 0, 255, 127),
|
||||||
|
"steelblue": Color( 70, 130, 180),
|
||||||
|
"tan": Color(210, 180, 140),
|
||||||
|
"teal": Color( 0, 128, 128),
|
||||||
|
"thistle": Color(216, 191, 216),
|
||||||
|
"tomato": Color(255, 99, 71),
|
||||||
|
"turquoise": Color( 64, 224, 208),
|
||||||
|
"violet": Color(238, 130, 238),
|
||||||
|
"wheat": Color(245, 222, 179),
|
||||||
|
"white": Color(255, 255, 255),
|
||||||
|
"whitesmoke": Color(245, 245, 245),
|
||||||
|
"yellow": Color(255, 255, 0),
|
||||||
|
"yellowgreen": Color(154, 205, 50)
|
||||||
|
})
|
||||||
|
for k, v in _svg_colors.items():
|
||||||
|
v.closest_name = v.name = k
|
||||||
|
|
||||||
|
# 2012-12-08T14:29-07:00: Copied over from Colors.hexstr_for_ccodes in the main
|
||||||
|
# textsub file, and subsequently modified.
|
||||||
|
_irc_colors.update({
|
||||||
|
# These are all taken from *MY* XChat settings - they aren't guaranteed to
|
||||||
|
# please everyone!
|
||||||
|
0: Color(0xFFFFFF),
|
||||||
|
1: Color(0x1F1F1F),
|
||||||
|
2: Color(0x00007F),
|
||||||
|
3: Color(0x007F00),
|
||||||
|
4: Color(0xFF0000),
|
||||||
|
5: Color(0x7F0000),
|
||||||
|
6: Color(0x9C009C),
|
||||||
|
7: Color(0xFC7F00),
|
||||||
|
8: Color(0xFFFF00),
|
||||||
|
9: Color(0x00FC00),
|
||||||
|
##10: Color(0x009393),
|
||||||
|
10: Color(0x008282),
|
||||||
|
11: Color(0x00FFFF),
|
||||||
|
12: Color(0x0000FC),
|
||||||
|
13: Color(0xFF00FF),
|
||||||
|
14: Color(0x7F7F7F),
|
||||||
|
15: Color(0xD2D2D2),
|
||||||
|
# My local colors
|
||||||
|
16: Color(0xCCCCCC),
|
||||||
|
##17: Color(0x000000), # Commented out 'til readability checks are in
|
||||||
|
17: Color(0x1F1F1F),
|
||||||
|
18: Color(0x000056),
|
||||||
|
19: Color(0x008141),
|
||||||
|
20: Color(0xE00707),
|
||||||
|
21: Color(0xA10000),
|
||||||
|
22: Color(0x6A006A),
|
||||||
|
23: Color(0xA15000),
|
||||||
|
24: Color(0xA1A100),
|
||||||
|
25: Color(0x416600),
|
||||||
|
##26: Color(0x008282),
|
||||||
|
26: Color(0x005682),
|
||||||
|
27: Color(0x00D5F2),
|
||||||
|
28: Color(0x0715CD),
|
||||||
|
29: Color(0x99004D),
|
||||||
|
30: Color(0x323232),
|
||||||
|
31: Color(0x929292),
|
||||||
|
|
||||||
|
99: Color(0x999999) # Until I think of a better solution to this
|
||||||
|
})
|
||||||
|
for k, v in _irc_colors.items():
|
||||||
|
v.ccode = "%02d" % k
|
||||||
|
del k, v
|
Loading…
Reference in a new issue