PageRenderTime 50ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/parsetools.py

https://github.com/602p/pesterchum
Python | 577 lines | 575 code | 2 blank | 0 comment | 0 complexity | c2cb26304935645b4a75b1dd8c06f0f1 MD5 | raw file
Possible License(s): GPL-3.0, Cube
  1. import re
  2. import random
  3. import ostools
  4. from copy import copy
  5. from datetime import timedelta
  6. from PyQt4 import QtGui
  7. from generic import mysteryTime
  8. from quirks import ScriptQuirks
  9. from pyquirks import PythonQuirks
  10. from luaquirks import LuaQuirks
  11. _ctag_begin = re.compile(r'(?i)<c=(.*?)>')
  12. _gtag_begin = re.compile(r'(?i)<g[a-f]>')
  13. _ctag_end = re.compile(r'(?i)</c>')
  14. _ctag_rgb = re.compile(r'\d+,\d+,\d+')
  15. _urlre = re.compile(r"(?i)(?:^|(?<=\s))(?:(?:https?|ftp)://|magnet:)[^\s]+")
  16. _url2re = re.compile(r"(?i)www\.[^\s]+")
  17. _memore = re.compile(r"(\s|^)(#[A-Za-z0-9_]+)")
  18. _handlere = re.compile(r"(\s|^)(@[A-Za-z0-9_]+)")
  19. _imgre = re.compile(r"""(?i)<img src=['"](\S+)['"]\s*/>""")
  20. _mecmdre = re.compile(r"^(/me|PESTERCHUM:ME)(\S*)")
  21. oocre = re.compile(r"[\[(][\[(].*[\])][\])]")
  22. _format_begin = re.compile(r'(?i)<([ibu])>')
  23. _format_end = re.compile(r'(?i)</([ibu])>')
  24. _honk = re.compile(r"(?i)\bhonk\b")
  25. quirkloader = ScriptQuirks()
  26. quirkloader.add(PythonQuirks())
  27. quirkloader.add(LuaQuirks())
  28. quirkloader.loadAll()
  29. print quirkloader.funcre()
  30. _functionre = re.compile(r"%s" % quirkloader.funcre())
  31. _groupre = re.compile(r"\\([0-9]+)")
  32. def reloadQuirkFunctions():
  33. quirkloader.loadAll()
  34. global _functionre
  35. _functionre = re.compile(r"%s" % quirkloader.funcre())
  36. def lexer(string, objlist):
  37. """objlist is a list: [(objecttype, re),...] list is in order of preference"""
  38. stringlist = [string]
  39. for (oType, regexp) in objlist:
  40. newstringlist = []
  41. for (stri, s) in enumerate(stringlist):
  42. if type(s) not in [str, unicode]:
  43. newstringlist.append(s)
  44. continue
  45. lasti = 0
  46. for m in regexp.finditer(s):
  47. start = m.start()
  48. end = m.end()
  49. tag = oType(m.group(0), *m.groups())
  50. if lasti != start:
  51. newstringlist.append(s[lasti:start])
  52. newstringlist.append(tag)
  53. lasti = end
  54. if lasti < len(string):
  55. newstringlist.append(s[lasti:])
  56. stringlist = copy(newstringlist)
  57. return stringlist
  58. class colorBegin(object):
  59. def __init__(self, string, color):
  60. self.string = string
  61. self.color = color
  62. def convert(self, format):
  63. color = self.color
  64. if format == "text":
  65. return ""
  66. if _ctag_rgb.match(color) is not None:
  67. if format=='ctag':
  68. return "<c=%s>" % (color)
  69. try:
  70. qc = QtGui.QColor(*[int(c) for c in color.split(",")])
  71. except ValueError:
  72. qc = QtGui.QColor("black")
  73. else:
  74. qc = QtGui.QColor(color)
  75. if not qc.isValid():
  76. qc = QtGui.QColor("black")
  77. if format == "html":
  78. return '<span style="color:%s">' % (qc.name())
  79. elif format == "bbcode":
  80. return '[color=%s]' % (qc.name())
  81. elif format == "ctag":
  82. (r,g,b,a) = qc.getRgb()
  83. return '<c=%s,%s,%s>' % (r,g,b)
  84. class colorEnd(object):
  85. def __init__(self, string):
  86. self.string = string
  87. def convert(self, format):
  88. if format == "html":
  89. return "</span>"
  90. elif format == "bbcode":
  91. return "[/color]"
  92. elif format == "text":
  93. return ""
  94. else:
  95. return self.string
  96. class formatBegin(object):
  97. def __init__(self, string, ftype):
  98. self.string = string
  99. self.ftype = ftype
  100. def convert(self, format):
  101. if format == "html":
  102. return "<%s>" % (self.ftype)
  103. elif format == "bbcode":
  104. return "[%s]" % (self.ftype)
  105. elif format == "text":
  106. return ""
  107. else:
  108. return self.string
  109. class formatEnd(object):
  110. def __init__(self, string, ftype):
  111. self.string = string
  112. self.ftype = ftype
  113. def convert(self, format):
  114. if format == "html":
  115. return "</%s>" % (self.ftype)
  116. elif format == "bbcode":
  117. return "[/%s]" % (self.ftype)
  118. elif format == "text":
  119. return ""
  120. else:
  121. return self.string
  122. class hyperlink(object):
  123. def __init__(self, string):
  124. self.string = string
  125. def convert(self, format):
  126. if format == "html":
  127. return "<a href='%s'>%s</a>" % (self.string, self.string)
  128. elif format == "bbcode":
  129. return "[url]%s[/url]" % (self.string)
  130. else:
  131. return self.string
  132. class hyperlink_lazy(hyperlink):
  133. def __init__(self, string):
  134. self.string = "http://" + string
  135. class imagelink(object):
  136. def __init__(self, string, img):
  137. self.string = string
  138. self.img = img
  139. def convert(self, format):
  140. if format == "html":
  141. return self.string
  142. elif format == "bbcode":
  143. if self.img[0:7] == "http://":
  144. return "[img]%s[/img]" % (self.img)
  145. else:
  146. return ""
  147. else:
  148. return ""
  149. class memolex(object):
  150. def __init__(self, string, space, channel):
  151. self.string = string
  152. self.space = space
  153. self.channel = channel
  154. def convert(self, format):
  155. if format == "html":
  156. return "%s<a href='%s'>%s</a>" % (self.space, self.channel, self.channel)
  157. else:
  158. return self.string
  159. class chumhandlelex(object):
  160. def __init__(self, string, space, handle):
  161. self.string = string
  162. self.space = space
  163. self.handle = handle
  164. def convert(self, format):
  165. if format == "html":
  166. return "%s<a href='%s'>%s</a>" % (self.space, self.handle, self.handle)
  167. else:
  168. return self.string
  169. class smiley(object):
  170. def __init__(self, string):
  171. self.string = string
  172. def convert(self, format):
  173. if format == "html":
  174. return "<img src='smilies/%s' alt='%s' title='%s' />" % (smiledict[self.string], self.string, self.string)
  175. else:
  176. return self.string
  177. class honker(object):
  178. def __init__(self, string):
  179. self.string = string
  180. def convert(self, format):
  181. if format == "html":
  182. return "<img src='smilies/honk.png' alt'honk' title='honk' />"
  183. else:
  184. return self.string
  185. class mecmd(object):
  186. def __init__(self, string, mecmd, suffix):
  187. self.string = string
  188. self.suffix = suffix
  189. def convert(self, format):
  190. return self.string
  191. def lexMessage(string):
  192. lexlist = [(mecmd, _mecmdre),
  193. (colorBegin, _ctag_begin), (colorBegin, _gtag_begin),
  194. (colorEnd, _ctag_end),
  195. (formatBegin, _format_begin), (formatEnd, _format_end),
  196. (imagelink, _imgre),
  197. (hyperlink, _urlre), (hyperlink_lazy, _url2re),
  198. (memolex, _memore),
  199. (chumhandlelex, _handlere),
  200. (smiley, _smilere),
  201. (honker, _honk)]
  202. string = unicode(string)
  203. string = string.replace("\n", " ").replace("\r", " ")
  204. lexed = lexer(unicode(string), lexlist)
  205. balanced = []
  206. beginc = 0
  207. endc = 0
  208. for o in lexed:
  209. if type(o) is colorBegin:
  210. beginc += 1
  211. balanced.append(o)
  212. elif type(o) is colorEnd:
  213. if beginc >= endc:
  214. endc += 1
  215. balanced.append(o)
  216. else:
  217. balanced.append(o.string)
  218. else:
  219. balanced.append(o)
  220. if beginc > endc:
  221. for i in range(0, beginc-endc):
  222. balanced.append(colorEnd("</c>"))
  223. if len(balanced) == 0:
  224. balanced.append("")
  225. if type(balanced[len(balanced)-1]) not in [str, unicode]:
  226. balanced.append("")
  227. return balanced
  228. def convertTags(lexed, format="html"):
  229. if format not in ["html", "bbcode", "ctag", "text"]:
  230. raise ValueError("Color format not recognized")
  231. if type(lexed) in [str, unicode]:
  232. lexed = lexMessage(lexed)
  233. escaped = ""
  234. firststr = True
  235. for (i, o) in enumerate(lexed):
  236. if type(o) in [str, unicode]:
  237. if format == "html":
  238. escaped += o.replace("&", "&amp;").replace(">", "&gt;").replace("<","&lt;")
  239. else:
  240. escaped += o
  241. else:
  242. escaped += o.convert(format)
  243. return escaped
  244. def splitMessage(msg, format="ctag"):
  245. """Splits message if it is too long."""
  246. # split long text lines
  247. buf = []
  248. for o in msg:
  249. if type(o) in [str, unicode] and len(o) > 200:
  250. for i in range(0, len(o), 200):
  251. buf.append(o[i:i+200])
  252. else:
  253. buf.append(o)
  254. msg = buf
  255. okmsg = []
  256. cbegintags = []
  257. output = []
  258. for o in msg:
  259. oldctag = None
  260. okmsg.append(o)
  261. if type(o) is colorBegin:
  262. cbegintags.append(o)
  263. elif type(o) is colorEnd:
  264. try:
  265. oldctag = cbegintags.pop()
  266. except IndexError:
  267. pass
  268. # yeah normally i'd do binary search but im lazy
  269. msglen = len(convertTags(okmsg, format)) + 4*(len(cbegintags))
  270. if msglen > 400:
  271. okmsg.pop()
  272. if type(o) is colorBegin:
  273. cbegintags.pop()
  274. elif type(o) is colorEnd and oldctag is not None:
  275. cbegintags.append(oldctag)
  276. if len(okmsg) == 0:
  277. output.append([o])
  278. else:
  279. tmp = []
  280. for color in cbegintags:
  281. okmsg.append(colorEnd("</c>"))
  282. tmp.append(color)
  283. output.append(okmsg)
  284. if type(o) is colorBegin:
  285. cbegintags.append(o)
  286. elif type(o) is colorEnd:
  287. try:
  288. cbegintags.pop()
  289. except IndexError:
  290. pass
  291. tmp.append(o)
  292. okmsg = tmp
  293. if len(okmsg) > 0:
  294. output.append(okmsg)
  295. return output
  296. def addTimeInitial(string, grammar):
  297. endofi = string.find(":")
  298. endoftag = string.find(">")
  299. # support Doc Scratch mode
  300. if (endoftag < 0 or endoftag > 16) or (endofi < 0 or endofi > 17):
  301. return string
  302. return string[0:endoftag+1]+grammar.pcf+string[endoftag+1:endofi]+grammar.number+string[endofi:]
  303. def timeProtocol(cmd):
  304. dir = cmd[0]
  305. if dir == "?":
  306. return mysteryTime(0)
  307. cmd = cmd[1:]
  308. cmd = re.sub("[^0-9:]", "", cmd)
  309. try:
  310. l = [int(x) for x in cmd.split(":")]
  311. except ValueError:
  312. l = [0,0]
  313. timed = timedelta(0, l[0]*3600+l[1]*60)
  314. if dir == "P":
  315. timed = timed*-1
  316. return timed
  317. def timeDifference(td):
  318. if type(td) is mysteryTime:
  319. return "??:?? FROM ????"
  320. if td < timedelta(0):
  321. when = "AGO"
  322. else:
  323. when = "FROM NOW"
  324. atd = abs(td)
  325. minutes = (atd.days*86400 + atd.seconds) // 60
  326. hours = minutes // 60
  327. leftoverminutes = minutes % 60
  328. if atd == timedelta(0):
  329. timetext = "RIGHT NOW"
  330. elif atd < timedelta(0,3600):
  331. if minutes == 1:
  332. timetext = "%d MINUTE %s" % (minutes, when)
  333. else:
  334. timetext = "%d MINUTES %s" % (minutes, when)
  335. elif atd < timedelta(0,3600*100):
  336. if hours == 1 and leftoverminutes == 0:
  337. timetext = "%d:%02d HOUR %s" % (hours, leftoverminutes, when)
  338. else:
  339. timetext = "%d:%02d HOURS %s" % (hours, leftoverminutes, when)
  340. else:
  341. timetext = "%d HOURS %s" % (hours, when)
  342. return timetext
  343. def nonerep(text):
  344. return text
  345. class parseLeaf(object):
  346. def __init__(self, function, parent):
  347. self.nodes = []
  348. self.function = function
  349. self.parent = parent
  350. def append(self, node):
  351. self.nodes.append(node)
  352. def expand(self, mo):
  353. out = ""
  354. for n in self.nodes:
  355. if type(n) == parseLeaf:
  356. out += n.expand(mo)
  357. elif type(n) == backreference:
  358. out += mo.group(int(n.number))
  359. else:
  360. out += n
  361. out = self.function(out)
  362. return out
  363. class backreference(object):
  364. def __init__(self, number):
  365. self.number = number
  366. def __str__(self):
  367. return self.number
  368. def parseRegexpFunctions(to):
  369. parsed = parseLeaf(nonerep, None)
  370. current = parsed
  371. curi = 0
  372. functiondict = quirkloader.quirks
  373. while curi < len(to):
  374. tmp = to[curi:]
  375. mo = _functionre.search(tmp)
  376. if mo is not None:
  377. if mo.start() > 0:
  378. current.append(to[curi:curi+mo.start()])
  379. backr = _groupre.search(mo.group())
  380. if backr is not None:
  381. current.append(backreference(backr.group(1)))
  382. elif mo.group()[:-1] in functiondict.keys():
  383. p = parseLeaf(functiondict[mo.group()[:-1]], current)
  384. current.append(p)
  385. current = p
  386. elif mo.group() == ")":
  387. if current.parent is not None:
  388. current = current.parent
  389. else:
  390. current.append(")")
  391. curi = mo.end()+curi
  392. else:
  393. current.append(to[curi:])
  394. curi = len(to)
  395. return parsed
  396. def img2smiley(string):
  397. string = unicode(string)
  398. def imagerep(mo):
  399. return reverse_smiley[mo.group(1)]
  400. string = re.sub(r'<img src="smilies/(\S+)" />', imagerep, string)
  401. return string
  402. smiledict = {
  403. ":rancorous:": "pc_rancorous.png",
  404. ":apple:": "apple.png",
  405. ":bathearst:": "bathearst.png",
  406. ":cathearst:": "cathearst.png",
  407. ":woeful:": "pc_bemused.png",
  408. ":sorrow:": "blacktear.png",
  409. ":pleasant:": "pc_pleasant.png",
  410. ":blueghost:": "blueslimer.gif",
  411. ":slimer:": "slimer.gif",
  412. ":candycorn:": "candycorn.png",
  413. ":cheer:": "cheer.gif",
  414. ":duhjohn:": "confusedjohn.gif",
  415. ":datrump:": "datrump.png",
  416. ":facepalm:": "facepalm.png",
  417. ":bonk:": "headbonk.gif",
  418. ":mspa:": "mspa_face.png",
  419. ":gun:": "mspa_reader.gif",
  420. ":cal:": "lilcal.png",
  421. ":amazedfirman:": "pc_amazedfirman.png",
  422. ":amazed:": "pc_amazed.png",
  423. ":chummy:": "pc_chummy.png",
  424. ":cool:": "pccool.png",
  425. ":smooth:": "pccool.png",
  426. ":distraughtfirman": "pc_distraughtfirman.png",
  427. ":distraught:": "pc_distraught.png",
  428. ":insolent:": "pc_insolent.png",
  429. ":bemused:": "pc_bemused.png",
  430. ":3:": "pckitty.png",
  431. ":mystified:": "pc_mystified.png",
  432. ":pranky:": "pc_pranky.png",
  433. ":tense:": "pc_tense.png",
  434. ":record:": "record.gif",
  435. ":squiddle:": "squiddle.gif",
  436. ":tab:": "tab.gif",
  437. ":beetip:": "theprofessor.png",
  438. ":flipout:": "weasel.gif",
  439. ":befuddled:": "what.png",
  440. ":pumpkin:": "whatpumpkin.png",
  441. ":trollcool:": "trollcool.png",
  442. ":jadecry:": "jadespritehead.gif",
  443. ":ecstatic:": "ecstatic.png",
  444. ":relaxed:": "relaxed.png",
  445. ":discontent:": "discontent.png",
  446. ":devious:": "devious.png",
  447. ":sleek:": "sleek.png",
  448. ":detestful:": "detestful.png",
  449. ":mirthful:": "mirthful.png",
  450. ":manipulative:": "manipulative.png",
  451. ":vigorous:": "vigorous.png",
  452. ":perky:": "perky.png",
  453. ":acceptant:": "acceptant.png",
  454. ":olliesouty:": "olliesouty.gif",
  455. ":billiards:": "poolballS.gif",
  456. ":billiardslarge:": "poolballL.gif",
  457. ":whatdidyoudo:": "whatdidyoudo.gif",
  458. ":brocool:": "pcstrider.png",
  459. ":trollbro:": "trollbro.png",
  460. ":playagame:": "saw.gif",
  461. ":trollc00l:": "trollc00l.gif",
  462. ":suckers:": "Suckers.gif",
  463. ":scorpio:": "scorpio.gif",
  464. ":shades:": "shades.png",
  465. }
  466. if ostools.isOSXBundle():
  467. for emote in smiledict:
  468. graphic = smiledict[emote]
  469. if graphic.find(".gif"):
  470. graphic = graphic.replace(".gif", ".png")
  471. smiledict[emote] = graphic
  472. reverse_smiley = dict((v,k) for k, v in smiledict.iteritems())
  473. _smilere = re.compile("|".join(smiledict.keys()))
  474. class ThemeException(Exception):
  475. def __init__(self, value):
  476. self.parameter = value
  477. def __str__(self):
  478. return repr(self.parameter)
  479. def themeChecker(theme):
  480. needs = ["main/size", "main/icon", "main/windowtitle", "main/style", \
  481. "main/background-image", "main/menubar/style", "main/menu/menuitem", \
  482. "main/menu/style", "main/menu/selected", "main/close/image", \
  483. "main/close/loc", "main/minimize/image", "main/minimize/loc", \
  484. "main/menu/loc", "main/menus/client/logviewer", \
  485. "main/menus/client/addgroup", "main/menus/client/options", \
  486. "main/menus/client/exit", "main/menus/client/userlist", \
  487. "main/menus/client/memos", "main/menus/client/import", \
  488. "main/menus/client/idle", "main/menus/client/reconnect", \
  489. "main/menus/client/_name", "main/menus/profile/quirks", \
  490. "main/menus/profile/block", "main/menus/profile/color", \
  491. "main/menus/profile/switch", "main/menus/profile/_name", \
  492. "main/menus/help/about", "main/menus/help/_name", "main/moodlabel/text", \
  493. "main/moodlabel/loc", "main/moodlabel/style", "main/moods", \
  494. "main/addchum/style", "main/addchum/text", "main/addchum/size", \
  495. "main/addchum/loc", "main/pester/text", "main/pester/size", \
  496. "main/pester/loc", "main/block/text", "main/block/size", "main/block/loc", \
  497. "main/mychumhandle/label/text", "main/mychumhandle/label/loc", \
  498. "main/mychumhandle/label/style", "main/mychumhandle/handle/loc", \
  499. "main/mychumhandle/handle/size", "main/mychumhandle/handle/style", \
  500. "main/mychumhandle/colorswatch/size", "main/mychumhandle/colorswatch/loc", \
  501. "main/defaultmood", "main/chums/size", "main/chums/loc", \
  502. "main/chums/style", "main/menus/rclickchumlist/pester", \
  503. "main/menus/rclickchumlist/removechum", \
  504. "main/menus/rclickchumlist/blockchum", "main/menus/rclickchumlist/viewlog", \
  505. "main/menus/rclickchumlist/removegroup", \
  506. "main/menus/rclickchumlist/renamegroup", \
  507. "main/menus/rclickchumlist/movechum", "convo/size", \
  508. "convo/tabwindow/style", "convo/tabs/tabstyle", "convo/tabs/style", \
  509. "convo/tabs/selectedstyle", "convo/style", "convo/margins", \
  510. "convo/chumlabel/text", "convo/chumlabel/style", "convo/chumlabel/align/h", \
  511. "convo/chumlabel/align/v", "convo/chumlabel/maxheight", \
  512. "convo/chumlabel/minheight", "main/menus/rclickchumlist/quirksoff", \
  513. "main/menus/rclickchumlist/addchum", "main/menus/rclickchumlist/blockchum", \
  514. "main/menus/rclickchumlist/unblockchum", \
  515. "main/menus/rclickchumlist/viewlog", "main/trollslum/size", \
  516. "main/trollslum/style", "main/trollslum/label/text", \
  517. "main/trollslum/label/style", "main/menus/profile/block", \
  518. "main/chums/moods/blocked/icon", "convo/systemMsgColor", \
  519. "convo/textarea/style", "convo/text/beganpester", "convo/text/ceasepester", \
  520. "convo/text/blocked", "convo/text/unblocked", "convo/text/blockedmsg", \
  521. "convo/text/idle", "convo/input/style", "memos/memoicon", \
  522. "memos/textarea/style", "memos/systemMsgColor", "convo/text/joinmemo", \
  523. "memos/input/style", "main/menus/rclickchumlist/banuser", \
  524. "main/menus/rclickchumlist/opuser", "main/menus/rclickchumlist/voiceuser", \
  525. "memos/margins", "convo/text/openmemo", "memos/size", "memos/style", \
  526. "memos/label/text", "memos/label/style", "memos/label/align/h", \
  527. "memos/label/align/v", "memos/label/maxheight", "memos/label/minheight", \
  528. "memos/userlist/style", "memos/userlist/width", "memos/time/text/width", \
  529. "memos/time/text/style", "memos/time/arrows/left", \
  530. "memos/time/arrows/style", "memos/time/buttons/style", \
  531. "memos/time/arrows/right", "memos/op/icon", "memos/voice/icon", \
  532. "convo/text/closememo", "convo/text/kickedmemo", \
  533. "main/chums/userlistcolor", "main/defaultwindow/style", \
  534. "main/chums/moods", "main/chums/moods/chummy/icon", "main/menus/help/help", \
  535. "main/menus/help/calsprite", "main/menus/help/nickserv", "main/menus/help/chanserv", \
  536. "main/menus/rclickchumlist/invitechum", "main/menus/client/randen", \
  537. "main/menus/rclickchumlist/memosetting", "main/menus/rclickchumlist/memonoquirk", \
  538. "main/menus/rclickchumlist/memohidden", "main/menus/rclickchumlist/memoinvite", \
  539. "main/menus/rclickchumlist/memomute", "main/menus/rclickchumlist/notes"]
  540. for n in needs:
  541. try:
  542. theme[n]
  543. except KeyError:
  544. raise ThemeException("Missing theme requirement: %s" % (n))