pesterchum/parsetools.py

561 lines
19 KiB
Python
Raw Normal View History

2011-02-04 16:17:27 -05:00
import re
2011-04-14 03:07:05 -04:00
import random
import ostools
2011-02-11 04:07:07 -05:00
from copy import copy
from datetime import timedelta
2011-02-04 16:17:27 -05:00
from PyQt4 import QtGui
2011-02-13 20:32:02 -05:00
from generic import mysteryTime
2011-06-07 11:48:35 -04:00
from pyquirks import PythonQuirks
2011-02-13 20:32:02 -05:00
2011-02-10 13:00:06 -05:00
_ctag_begin = re.compile(r'(?i)<c=(.*?)>')
2011-02-13 04:27:12 -05:00
_gtag_begin = re.compile(r'(?i)<g[a-f]>')
2011-02-11 18:37:31 -05:00
_ctag_end = re.compile(r'(?i)</c>')
2011-02-04 16:17:27 -05:00
_ctag_rgb = re.compile(r'\d+,\d+,\d+')
_urlre = re.compile(r"(?i)(?:^|(?<=\s))(?:(?:https?|ftp)://|magnet:)[^\s]+")
2011-12-24 13:45:05 -05:00
_url2re = re.compile(r"(?i)www\.[^\s]+")
2011-02-13 04:27:12 -05:00
_memore = re.compile(r"(\s|^)(#[A-Za-z0-9_]+)")
2011-04-12 19:54:01 -04:00
_handlere = re.compile(r"(\s|^)(@[A-Za-z0-9_]+)")
2011-02-13 04:27:12 -05:00
_imgre = re.compile(r"""(?i)<img src=['"](\S+)['"]\s*/>""")
2011-02-24 13:07:37 -05:00
_mecmdre = re.compile(r"^(/me|PESTERCHUM:ME)(\S*)")
oocre = re.compile(r"[\[(][\[(].*[\])][\])]")
2012-03-14 00:24:13 -04:00
_format_begin = re.compile(r'(?i)<([ibu])>')
_format_end = re.compile(r'(?i)</([ibu])>')
2011-02-04 16:17:27 -05:00
2011-06-07 11:48:35 -04:00
quirkloader = PythonQuirks()
_functionre = re.compile(r"%s" % quirkloader.funcre())
2011-04-14 03:07:05 -04:00
_groupre = re.compile(r"\\([0-9]+)")
2011-06-07 11:48:35 -04:00
def reloadQuirkFunctions():
quirkloader.load()
global _functionre
_functionre = re.compile(r"%s" % quirkloader.funcre())
2011-02-11 04:07:07 -05:00
def lexer(string, objlist):
"""objlist is a list: [(objecttype, re),...] list is in order of preference"""
stringlist = [string]
for (oType, regexp) in objlist:
2011-02-11 18:37:31 -05:00
newstringlist = []
2011-02-11 04:07:07 -05:00
for (stri, s) in enumerate(stringlist):
2011-02-11 18:37:31 -05:00
if type(s) not in [str, unicode]:
newstringlist.append(s)
continue
2011-02-11 04:07:07 -05:00
lasti = 0
2011-02-11 18:37:31 -05:00
for m in regexp.finditer(s):
2011-02-11 04:07:07 -05:00
start = m.start()
end = m.end()
2011-02-11 18:37:31 -05:00
tag = oType(m.group(0), *m.groups())
if lasti != start:
newstringlist.append(s[lasti:start])
newstringlist.append(tag)
lasti = end
2011-02-11 04:07:07 -05:00
if lasti < len(string):
2011-02-11 18:37:31 -05:00
newstringlist.append(s[lasti:])
2011-02-11 04:07:07 -05:00
stringlist = copy(newstringlist)
return stringlist
2011-02-11 18:37:31 -05:00
class colorBegin(object):
def __init__(self, string, color):
self.string = string
self.color = color
def convert(self, format):
color = self.color
2011-02-13 04:27:12 -05:00
if format == "text":
return ""
2011-02-04 16:17:27 -05:00
if _ctag_rgb.match(color) is not None:
if format=='ctag':
2011-02-04 19:50:56 -05:00
return "<c=%s>" % (color)
2011-02-04 16:17:27 -05:00
try:
qc = QtGui.QColor(*[int(c) for c in color.split(",")])
except ValueError:
qc = QtGui.QColor("black")
else:
qc = QtGui.QColor(color)
if not qc.isValid():
qc = QtGui.QColor("black")
if format == "html":
return '<span style="color:%s">' % (qc.name())
elif format == "bbcode":
return '[color=%s]' % (qc.name())
elif format == "ctag":
(r,g,b,a) = qc.getRgb()
return '<c=%s,%s,%s>' % (r,g,b)
2011-02-11 18:37:31 -05:00
class colorEnd(object):
def __init__(self, string):
self.string = string
def convert(self, format):
if format == "html":
return "</span>"
elif format == "bbcode":
return "[/color]"
2011-02-13 04:27:12 -05:00
elif format == "text":
return ""
2011-02-11 18:37:31 -05:00
else:
return self.string
2012-03-14 00:24:13 -04:00
class formatBegin(object):
def __init__(self, string, ftype):
self.string = string
self.ftype = ftype
def convert(self, format):
if format == "html":
return "<%s>" % (self.ftype)
elif format == "bbcode":
return "[%s]" % (self.ftype)
elif format == "text":
return ""
else:
return self.string
class formatEnd(object):
def __init__(self, string, ftype):
self.string = string
self.ftype = ftype
def convert(self, format):
if format == "html":
return "</%s>" % (self.ftype)
elif format == "bbcode":
return "[/%s]" % (self.ftype)
elif format == "text":
return ""
else:
return self.string
2011-02-11 18:37:31 -05:00
class hyperlink(object):
def __init__(self, string):
self.string = string
def convert(self, format):
if format == "html":
return "<a href='%s'>%s</a>" % (self.string, self.string)
elif format == "bbcode":
return "[url]%s[/url]" % (self.string)
else:
return self.string
class hyperlink_lazy(hyperlink):
def __init__(self, string):
self.string = "http://" + string
2011-02-13 04:27:12 -05:00
class imagelink(object):
def __init__(self, string, img):
self.string = string
self.img = img
def convert(self, format):
if format == "html":
return self.string
elif format == "bbcode":
if self.img[0:7] == "http://":
2011-03-03 04:41:51 -05:00
return "[img]%s[/img]" % (self.img)
2011-02-13 04:27:12 -05:00
else:
return ""
else:
return ""
class memolex(object):
def __init__(self, string, space, channel):
self.string = string
self.space = space
self.channel = channel
def convert(self, format):
if format == "html":
return "%s<a href='%s'>%s</a>" % (self.space, self.channel, self.channel)
else:
return self.string
2011-04-12 19:54:01 -04:00
class chumhandlelex(object):
def __init__(self, string, space, handle):
self.string = string
self.space = space
self.handle = handle
def convert(self, format):
if format == "html":
return "%s<a href='%s'>%s</a>" % (self.space, self.handle, self.handle)
else:
return self.string
2011-02-11 18:37:31 -05:00
class smiley(object):
def __init__(self, string):
self.string = string
def convert(self, format):
if format == "html":
return "<img src='smilies/%s' alt='%s' title='%s' />" % (smiledict[self.string], self.string, self.string)
2011-02-11 18:37:31 -05:00
else:
return self.string
2011-02-13 04:27:12 -05:00
class mecmd(object):
2011-02-24 13:07:37 -05:00
def __init__(self, string, mecmd, suffix):
2011-02-13 04:27:12 -05:00
self.string = string
self.suffix = suffix
def convert(self, format):
return self.string
2011-02-04 16:17:27 -05:00
2011-02-13 04:27:12 -05:00
def lexMessage(string):
lexlist = [(mecmd, _mecmdre),
(colorBegin, _ctag_begin), (colorBegin, _gtag_begin),
2012-03-14 00:24:13 -04:00
(colorEnd, _ctag_end),
(formatBegin, _format_begin), (formatEnd, _format_end),
(imagelink, _imgre),
(hyperlink, _urlre), (hyperlink_lazy, _url2re),
2011-12-24 01:15:22 -05:00
(memolex, _memore),
2011-04-12 19:54:01 -04:00
(chumhandlelex, _handlere),
2011-02-11 18:37:31 -05:00
(smiley, _smilere)]
2011-04-10 05:22:06 -04:00
string = unicode(string)
string = string.replace("\n", " ").replace("\r", " ")
2011-02-13 04:27:12 -05:00
lexed = lexer(unicode(string), lexlist)
2011-02-11 18:37:31 -05:00
balanced = []
beginc = 0
endc = 0
for o in lexed:
if type(o) is colorBegin:
beginc += 1
balanced.append(o)
elif type(o) is colorEnd:
if beginc >= endc:
endc += 1
balanced.append(o)
else:
balanced.append(o.string)
2011-02-04 16:17:27 -05:00
else:
2011-02-11 18:37:31 -05:00
balanced.append(o)
if beginc > endc:
for i in range(0, beginc-endc):
balanced.append(colorEnd("</c>"))
2011-02-21 14:07:59 -05:00
if len(balanced) == 0:
balanced.append("")
2011-02-13 04:27:12 -05:00
if type(balanced[len(balanced)-1]) not in [str, unicode]:
balanced.append("")
return balanced
def convertTags(lexed, format="html"):
if format not in ["html", "bbcode", "ctag", "text"]:
raise ValueError("Color format not recognized")
2011-02-11 18:37:31 -05:00
2011-02-13 04:27:12 -05:00
if type(lexed) in [str, unicode]:
lexed = lexMessage(lexed)
2011-02-11 18:37:31 -05:00
escaped = ""
2011-02-13 04:27:12 -05:00
firststr = True
for (i, o) in enumerate(lexed):
2011-02-11 18:37:31 -05:00
if type(o) in [str, unicode]:
if format == "html":
escaped += o.replace("&", "&amp;").replace(">", "&gt;").replace("<","&lt;")
2011-02-04 16:17:27 -05:00
else:
2011-02-11 18:37:31 -05:00
escaped += o
2011-02-04 16:17:27 -05:00
else:
2011-02-11 18:37:31 -05:00
escaped += o.convert(format)
2011-02-13 04:27:12 -05:00
2011-02-11 18:37:31 -05:00
return escaped
2011-04-10 05:22:06 -04:00
def splitMessage(msg, format="ctag"):
"""Splits message if it is too long."""
2011-04-13 02:12:19 -04:00
# split long text lines
buf = []
for o in msg:
if type(o) in [str, unicode] and len(o) > 200:
for i in range(0, len(o), 200):
buf.append(o[i:i+200])
else:
buf.append(o)
msg = buf
2011-04-10 05:22:06 -04:00
okmsg = []
cbegintags = []
output = []
for o in msg:
2011-04-14 03:07:05 -04:00
oldctag = None
2011-04-10 05:22:06 -04:00
okmsg.append(o)
if type(o) is colorBegin:
cbegintags.append(o)
elif type(o) is colorEnd:
2011-04-14 03:07:05 -04:00
try:
oldctag = cbegintags.pop()
except IndexError:
pass
2011-04-10 05:22:06 -04:00
# yeah normally i'd do binary search but im lazy
msglen = len(convertTags(okmsg, format)) + 4*(len(cbegintags))
if msglen > 400:
okmsg.pop()
2011-04-14 03:07:05 -04:00
if type(o) is colorBegin:
cbegintags.pop()
elif type(o) is colorEnd and oldctag is not None:
cbegintags.append(oldctag)
2011-04-10 05:22:06 -04:00
if len(okmsg) == 0:
output.append([o])
else:
tmp = []
for color in cbegintags:
okmsg.append(colorEnd("</c>"))
tmp.append(color)
output.append(okmsg)
if type(o) is colorBegin:
cbegintags.append(o)
elif type(o) is colorEnd:
2011-04-14 03:07:05 -04:00
try:
cbegintags.pop()
except IndexError:
pass
2011-04-10 05:22:06 -04:00
tmp.append(o)
okmsg = tmp
if len(okmsg) > 0:
output.append(okmsg)
return output
2011-05-10 02:33:59 -04:00
def addTimeInitial(string, grammar):
endofi = string.find(":")
endoftag = string.find(">")
2011-02-13 04:27:12 -05:00
# support Doc Scratch mode
if (endoftag < 0 or endoftag > 16) or (endofi < 0 or endofi > 17):
return string
return string[0:endoftag+1]+grammar.pcf+string[endoftag+1:endofi]+grammar.number+string[endofi:]
def timeProtocol(cmd):
dir = cmd[0]
2011-02-13 20:32:02 -05:00
if dir == "?":
return mysteryTime(0)
cmd = cmd[1:]
cmd = re.sub("[^0-9:]", "", cmd)
try:
l = [int(x) for x in cmd.split(":")]
except ValueError:
l = [0,0]
timed = timedelta(0, l[0]*3600+l[1]*60)
if dir == "P":
timed = timed*-1
return timed
2011-02-05 13:56:25 -05:00
def timeDifference(td):
2011-02-13 20:32:02 -05:00
if type(td) is mysteryTime:
return "??:?? FROM ????"
2011-02-05 13:56:25 -05:00
if td < timedelta(0):
when = "AGO"
else:
when = "FROM NOW"
atd = abs(td)
minutes = (atd.days*86400 + atd.seconds) // 60
hours = minutes // 60
leftoverminutes = minutes % 60
if atd == timedelta(0):
timetext = "RIGHT NOW"
elif atd < timedelta(0,3600):
2011-02-06 01:02:39 -05:00
if minutes == 1:
timetext = "%d MINUTE %s" % (minutes, when)
else:
2011-02-06 01:02:39 -05:00
timetext = "%d MINUTES %s" % (minutes, when)
2011-02-05 13:56:25 -05:00
elif atd < timedelta(0,3600*100):
2011-02-06 01:02:39 -05:00
if hours == 1 and leftoverminutes == 0:
timetext = "%d:%02d HOUR %s" % (hours, leftoverminutes, when)
else:
timetext = "%d:%02d HOURS %s" % (hours, leftoverminutes, when)
2011-02-05 13:56:25 -05:00
else:
timetext = "%d HOURS %s" % (hours, when)
return timetext
2011-02-08 17:47:07 -05:00
2011-04-14 03:07:05 -04:00
def nonerep(text):
return text
class parseLeaf(object):
def __init__(self, function, parent):
self.nodes = []
self.function = function
self.parent = parent
def append(self, node):
self.nodes.append(node)
def expand(self, mo):
out = ""
for n in self.nodes:
if type(n) == parseLeaf:
out += n.expand(mo)
elif type(n) == backreference:
out += mo.group(int(n.number))
else:
out += n
out = self.function(out)
return out
class backreference(object):
def __init__(self, number):
self.number = number
def __str__(self):
return self.number
def parseRegexpFunctions(to):
parsed = parseLeaf(nonerep, None)
current = parsed
curi = 0
2011-06-07 11:48:35 -04:00
functiondict = quirkloader.quirks
2011-04-14 03:07:05 -04:00
while curi < len(to):
tmp = to[curi:]
mo = _functionre.search(tmp)
if mo is not None:
if mo.start() > 0:
current.append(to[curi:curi+mo.start()])
backr = _groupre.search(mo.group())
if backr is not None:
current.append(backreference(backr.group(1)))
elif mo.group() in functiondict.keys():
p = parseLeaf(functiondict[mo.group()], current)
current.append(p)
current = p
elif mo.group() == ")":
if current.parent is not None:
current = current.parent
else:
current.append(")")
curi = mo.end()+curi
else:
current.append(to[curi:])
curi = len(to)
return parsed
2011-05-10 02:33:59 -04:00
2011-04-14 03:07:05 -04:00
2011-02-23 06:06:00 -05:00
def img2smiley(string):
string = unicode(string)
def imagerep(mo):
return reverse_smiley[mo.group(1)]
string = re.sub(r'<img src="smilies/(\S+)" />', imagerep, string)
2011-02-23 06:06:00 -05:00
return string
2011-02-08 17:47:07 -05:00
smiledict = {
":rancorous:": "pc_rancorous.png",
":apple:": "apple.png",
":bathearst:": "bathearst.png",
2011-02-22 19:49:47 -05:00
":cathearst:": "cathearst.png",
":woeful:": "pc_bemused.png",
":sorrow:": "blacktear.png",
":pleasant:": "pc_pleasant.png",
2011-02-08 17:47:07 -05:00
":blueghost:": "blueslimer.gif",
2011-02-14 15:04:57 -05:00
":slimer:": "slimer.gif",
":candycorn:": "candycorn.png",
":cheer:": "cheer.gif",
2011-02-08 17:47:07 -05:00
":duhjohn:": "confusedjohn.gif",
":datrump:": "datrump.png",
":facepalm:": "facepalm.png",
2011-02-08 17:47:07 -05:00
":bonk:": "headbonk.gif",
":mspa:": "mspa_face.png",
2011-02-08 17:47:07 -05:00
":gun:": "mspa_reader.gif",
":cal:": "lilcal.png",
":amazedfirman:": "pc_amazedfirman.png",
":amazed:": "pc_amazed.png",
":chummy:": "pc_chummy.png",
":cool:": "pccool.png",
":smooth:": "pccool.png",
":distraughtfirman": "pc_distraughtfirman.png",
":distraught:": "pc_distraught.png",
":insolent:": "pc_insolent.png",
":bemused:": "pc_bemused.png",
":3:": "pckitty.png",
":mystified:": "pc_mystified.png",
":pranky:": "pc_pranky.png",
":tense:": "pc_tense.png",
2011-02-08 17:47:07 -05:00
":record:": "record.gif",
":squiddle:": "squiddle.gif",
":tab:": "tab.gif",
":beetip:": "theprofessor.png",
2011-02-08 17:47:07 -05:00
":flipout:": "weasel.gif",
":befuddled:": "what.png",
":pumpkin:": "whatpumpkin.png",
":trollcool:": "trollcool.png",
2011-03-05 20:21:45 -05:00
":jadecry:": "jadespritehead.gif",
2011-03-07 19:14:21 -05:00
":ecstatic:": "ecstatic.png",
":relaxed:": "relaxed.png",
":discontent:": "discontent.png",
":devious:": "devious.png",
":sleek:": "sleek.png",
":detestful:": "detestful.png",
":mirthful:": "mirthful.png",
":manipulative:": "manipulative.png",
":vigorous:": "vigorous.png",
":perky:": "perky.png",
":acceptant:": "acceptant.png",
2011-10-25 02:37:49 -04:00
":olliesouty:": "olliesouty.gif",
2011-11-04 03:36:51 -04:00
":billiards:": "poolballS.gif",
":billiardslarge:": "poolballL.gif",
2011-12-24 01:15:22 -05:00
":whatdidyoudo:": "whatdidyoudo.gif",
2012-01-23 03:59:31 -05:00
":brocool:": "pcstrider.png",
2012-02-03 03:28:37 -05:00
":trollbro:": "trollbro.png",
2012-02-04 20:24:10 -05:00
":playagame:": "saw.gif",
2012-03-31 21:34:15 -04:00
":trollc00l:": "trollc00l.gif",
2012-04-09 23:44:28 -04:00
":suckers:": "Suckers.gif",
2012-04-20 02:38:41 -04:00
":scorpio:": "scorpio.gif",
2011-03-05 20:21:45 -05:00
}
2011-02-11 18:37:31 -05:00
if ostools.isOSXBundle():
for emote in smiledict:
graphic = smiledict[emote]
if graphic.find(".gif"):
graphic = graphic.replace(".gif", ".png")
smiledict[emote] = graphic
2011-02-23 06:06:00 -05:00
reverse_smiley = dict((v,k) for k, v in smiledict.iteritems())
2011-02-11 18:37:31 -05:00
_smilere = re.compile("|".join(smiledict.keys()))
2011-05-10 02:33:59 -04:00
class ThemeException(Exception):
def __init__(self, value):
self.parameter = value
def __str__(self):
return repr(self.parameter)
def themeChecker(theme):
needs = ["main/size", "main/icon", "main/windowtitle", "main/style", \
"main/background-image", "main/menubar/style", "main/menu/menuitem", \
"main/menu/style", "main/menu/selected", "main/close/image", \
"main/close/loc", "main/minimize/image", "main/minimize/loc", \
"main/menu/loc", "main/menus/client/logviewer", \
"main/menus/client/addgroup", "main/menus/client/options", \
"main/menus/client/exit", "main/menus/client/userlist", \
"main/menus/client/memos", "main/menus/client/import", \
"main/menus/client/idle", "main/menus/client/reconnect", \
"main/menus/client/_name", "main/menus/profile/quirks", \
"main/menus/profile/block", "main/menus/profile/color", \
"main/menus/profile/switch", "main/menus/profile/_name", \
"main/menus/help/about", "main/menus/help/_name", "main/moodlabel/text", \
"main/moodlabel/loc", "main/moodlabel/style", "main/moods", \
"main/addchum/style", "main/addchum/text", "main/addchum/size", \
"main/addchum/loc", "main/pester/text", "main/pester/size", \
"main/pester/loc", "main/block/text", "main/block/size", "main/block/loc", \
"main/mychumhandle/label/text", "main/mychumhandle/label/loc", \
"main/mychumhandle/label/style", "main/mychumhandle/handle/loc", \
"main/mychumhandle/handle/size", "main/mychumhandle/handle/style", \
"main/mychumhandle/colorswatch/size", "main/mychumhandle/colorswatch/loc", \
"main/defaultmood", "main/chums/size", "main/chums/loc", \
"main/chums/style", "main/menus/rclickchumlist/pester", \
"main/menus/rclickchumlist/removechum", \
"main/menus/rclickchumlist/blockchum", "main/menus/rclickchumlist/viewlog", \
"main/menus/rclickchumlist/removegroup", \
"main/menus/rclickchumlist/renamegroup", \
"main/menus/rclickchumlist/movechum", "convo/size", \
"convo/tabwindow/style", "convo/tabs/tabstyle", "convo/tabs/style", \
"convo/tabs/selectedstyle", "convo/style", "convo/margins", \
"convo/chumlabel/text", "convo/chumlabel/style", "convo/chumlabel/align/h", \
"convo/chumlabel/align/v", "convo/chumlabel/maxheight", \
"convo/chumlabel/minheight", "main/menus/rclickchumlist/quirksoff", \
"main/menus/rclickchumlist/addchum", "main/menus/rclickchumlist/blockchum", \
"main/menus/rclickchumlist/unblockchum", \
"main/menus/rclickchumlist/viewlog", "main/trollslum/size", \
"main/trollslum/style", "main/trollslum/label/text", \
"main/trollslum/label/style", "main/menus/profile/block", \
"main/chums/moods/blocked/icon", "convo/systemMsgColor", \
"convo/textarea/style", "convo/text/beganpester", "convo/text/ceasepester", \
"convo/text/blocked", "convo/text/unblocked", "convo/text/blockedmsg", \
"convo/text/idle", "convo/input/style", "memos/memoicon", \
"memos/textarea/style", "memos/systemMsgColor", "convo/text/joinmemo", \
"memos/input/style", "main/menus/rclickchumlist/banuser", \
"main/menus/rclickchumlist/opuser", "main/menus/rclickchumlist/voiceuser", \
"memos/margins", "convo/text/openmemo", "memos/size", "memos/style", \
"memos/label/text", "memos/label/style", "memos/label/align/h", \
"memos/label/align/v", "memos/label/maxheight", "memos/label/minheight", \
"memos/userlist/style", "memos/userlist/width", "memos/time/text/width", \
"memos/time/text/style", "memos/time/arrows/left", \
"memos/time/arrows/style", "memos/time/buttons/style", \
"memos/time/arrows/right", "memos/op/icon", "memos/voice/icon", \
"convo/text/closememo", "convo/text/kickedmemo", \
"main/chums/userlistcolor", "main/defaultwindow/style", \
"main/chums/moods", "main/chums/moods/chummy/icon", "main/menus/help/help", \
"main/menus/help/calsprite", "main/menus/help/nickserv", \
2011-07-12 03:15:47 -04:00
"main/menus/rclickchumlist/invitechum", "main/menus/client/randen", \
"main/menus/rclickchumlist/memosetting", "main/menus/rclickchumlist/memonoquirk", \
"main/menus/rclickchumlist/memohidden", "main/menus/rclickchumlist/memoinvite", \
2011-08-22 04:13:43 -04:00
"main/menus/rclickchumlist/memomute", "main/menus/rclickchumlist/notes"]
2011-05-10 02:33:59 -04:00
for n in needs:
try:
theme[n]
except KeyError:
raise ThemeException("Missing theme requirement: %s" % (n))