gtk: don't use gtk if there's no display
[urk.git] / parse_mirc.py
blob186ff9b2621383acbb0a4f869c0841d99b8cb151
1 try:
2 from conf import conf
3 except ImportError:
4 conf = {}
6 BOLD = '\x02'
7 UNDERLINE = '\x1F'
8 MIRC_COLOR = '\x03'
9 MIRC_COLOR_BG = MIRC_COLOR, MIRC_COLOR
10 BERS_COLOR = '\x04'
11 RESET = '\x0F'
13 colors = (
14 '#FFFFFF', '#000000', '#00007F', '#009300',
15 '#FF0000', '#7F0000', '#9C009C', '#FF7F00',
16 '#FFFF00', '#00FF00', '#009393', '#00FFFF',
17 '#0000FF', '#FF00FF', '#7F7F7F', '#D2D2D2'
20 def get_mirc_color(number):
21 if number == '99':
22 return None
24 number = int(number) & 15
26 confcolors = conf.get('colors', colors)
27 try:
28 return confcolors[number]
29 except:
30 # someone edited their colors wrongly
31 return colors[number]
33 DEC_DIGITS, HEX_DIGITS = set('0123456789'), set('0123456789abcdefABCDEF')
35 def parse_mirc_color(string, pos, open_tags, tags):
36 color_chars = 1
38 if MIRC_COLOR in open_tags:
39 fgtag = open_tags.pop(MIRC_COLOR)
40 fgtag['to'] = pos
41 tags.append(fgtag)
43 if MIRC_COLOR_BG in open_tags:
44 bgtag = open_tags.pop(MIRC_COLOR_BG)
45 bgtag['to'] = pos
46 tags.append(bgtag)
48 bg = bgtag['data'][1]
49 else:
50 bg = None
52 if string[0] in DEC_DIGITS:
53 if string[1] in DEC_DIGITS:
54 fg = get_mirc_color(string[:2])
55 string = string[1:]
56 color_chars += 2
58 else:
59 fg = get_mirc_color(string[0])
60 color_chars += 1
62 if string[1] == "," and string[2] in DEC_DIGITS:
63 if string[3] in DEC_DIGITS:
64 bg = get_mirc_color(string[2:4])
65 color_chars += 3
67 else:
68 bg = get_mirc_color(string[2])
69 color_chars += 2
71 else:
72 fg = bg = None
74 if fg:
75 open_tags[MIRC_COLOR] = {'data': ("foreground",fg), 'from': pos}
76 else:
77 open_tags.pop(MIRC_COLOR,None)
79 if bg:
80 open_tags[MIRC_COLOR_BG] = {'data': ("background",bg), 'from': pos}
81 else:
82 open_tags.pop(MIRC_COLOR_BG,None)
84 return color_chars
86 def parse_bersirc_color(string, pos, open_tags, tags):
87 bg = None
88 if MIRC_COLOR in open_tags:
89 tag = open_tags.pop(MIRC_COLOR)
90 tag['to'] = pos
91 tags.append(tag)
93 if MIRC_COLOR_BG in open_tags:
94 bgtag = open_tags.pop(MIRC_COLOR_BG)
95 bgtag['to'] = pos
96 tags.append(bgtag)
98 bg = bgtag['data'][1]
100 for c in (0, 1, 2, 3, 4, 5):
101 if string[c] not in HEX_DIGITS:
102 return 1
103 fg = '#' + string[:6].upper()
105 color_chars = 7
106 for c in (7, 8, 9, 10, 11, 12):
107 if string[c] not in HEX_DIGITS:
108 break
109 else:
110 if string[6] == ",":
111 bg = '#' + string[7:13].upper()
112 color_chars = 14
114 if fg:
115 open_tags[MIRC_COLOR] = {'data': ("foreground",fg), 'from': pos}
116 else:
117 open_tags.pop(MIRC_COLOR,None)
119 if bg:
120 open_tags[MIRC_COLOR_BG] = {'data': ("background",bg), 'from': pos}
121 else:
122 open_tags.pop(MIRC_COLOR_BG,None)
124 return color_chars
126 def parse_bold(string, pos, open_tags, tags):
127 if BOLD in open_tags:
128 tag = open_tags.pop(BOLD)
129 tag['to'] = pos
130 tags.append(tag)
132 else:
133 open_tags[BOLD] = {'data': ('weight', BOLD), 'from': pos}
135 return 1
137 def parse_underline(string, pos, open_tags, tags):
138 if UNDERLINE in open_tags:
139 tag = open_tags.pop(UNDERLINE)
140 tag['to'] = pos
141 tags.append(tag)
143 else:
144 open_tags[UNDERLINE] = {'data': ('underline', UNDERLINE), 'from': pos}
146 return 1
148 def parse_reset(string, pos, open_tags, tags):
149 for t in open_tags:
150 tag = open_tags[t]
151 tag['to'] = pos
152 tags.append(tag)
154 open_tags.clear()
156 return 1
158 tag_parser = {
159 MIRC_COLOR: parse_mirc_color,
160 BERS_COLOR: parse_bersirc_color,
161 BOLD: parse_bold,
162 UNDERLINE: parse_underline,
163 RESET: parse_reset
166 def parse_mirc(string):
167 string += RESET
169 out = ''
170 open_tags = {}
171 tags = []
172 text_i = outtext_i = 0
174 for tag_i, char in enumerate(string):
175 if char in tag_parser:
176 out += string[text_i:tag_i]
178 outtext_i += tag_i - text_i
180 text_i = tag_i + tag_parser[char](
181 string[tag_i+1:],
182 outtext_i,
183 open_tags,
184 tags
187 return tags, out
189 #transforms for unparse_mirc
192 def transform_reset(start, end):
193 return RESET, '', {}
196 def transform_color_reset(start, end):
197 if ('foreground' in start and 'foreground' not in end) or \
198 ('background' in start and 'background' not in end):
199 result = start.copy()
200 result.pop("foreground",None)
201 result.pop("background",None)
202 return MIRC_COLOR, DEC_DIGITS, result
203 else:
204 return '','',start
206 #^KXX
207 def transform_color(start, end):
208 if (start.get('foreground',99) != end.get('foreground',99)):
209 confcolors = conf.get('colors', colors)
210 result = start.copy()
211 if 'foreground' in end:
212 try:
213 index = list(confcolors).index(end['foreground'].upper())
214 except ValueError:
215 return '','',start
216 result['foreground'] = end['foreground']
217 else:
218 index = 99
219 del result['foreground']
220 return '\x03%02i' % index, ',', result
221 else:
222 return '','',start
224 #^KXX,YY
225 def transform_bcolor(start, end):
226 if (start.get('background',99) != end.get('background',99)):
227 confcolors = conf.get('colors', colors)
228 result = start.copy()
229 if 'foreground' in end:
230 try:
231 fg_index = list(confcolors).index(end['foreground'].upper())
232 except ValueError:
233 return '','',start
234 result['foreground'] = end['foreground']
235 else:
236 fg_index = 99
237 result.pop('foreground',None)
238 if 'background' in end:
239 try:
240 bg_index = list(confcolors).index(end['background'].upper())
241 except ValueError:
242 return '','',start
243 result['background'] = end['background']
244 else:
245 bg_index = 99
246 del result['background']
247 return '\x03%02i,%02i' % (fg_index, bg_index), ',', result
248 else:
249 return '','',start
251 #^LXXXXXX
252 def transform_bersirc(start, end):
253 if 'foreground' in end and end['foreground'] != start.get('foreground'):
254 result = start.copy()
255 result['foreground'] = end['foreground']
256 return "\x04%s" % end['foreground'][1:], ',', result
257 else:
258 return '','',start
260 #^LXXXXXX,YYYYYY
261 def transform_bbersirc(start, end):
262 if 'foreground' in end and 'background' in end and (
263 end['foreground'] != start.get('foreground') or
264 end['background'] != start.get('background')):
265 result = start.copy()
266 result['foreground'] = end['foreground']
267 result['background'] = end['background']
268 return "\x04%s,%s" % (end['foreground'][1:], end['background'][1:]), ',', result
269 else:
270 return '','',start
274 def transform_underline(start, end):
275 if ('underline' in start) != ('underline' in end):
276 result = start.copy()
277 if 'underline' in start:
278 del result['underline']
279 else:
280 result['underline'] = UNDERLINE
281 return UNDERLINE, '', result
282 else:
283 return '','',start
286 def transform_bold(start, end):
287 if ('weight' in start) != ('weight' in end):
288 result = start.copy()
289 if 'weight' in start:
290 del result['weight']
291 else:
292 result['weight'] = BOLD
293 return BOLD, '', result
294 else:
295 return '','',start
297 #^B^B
298 #In some rare circumstances, we HAVE to do this to generate a working string
299 def transform_dbold(start, end):
300 return BOLD*2, '', start
302 #return the formatting needed to transform one set of format tags to another
303 def transform(start, end, nextchar=" "):
304 transform_functions = (
305 transform_reset, transform_color_reset, transform_color, transform_bcolor,
306 transform_bersirc, transform_bbersirc, transform_underline,
307 transform_bold, transform_dbold,
310 candidates = [('','',start)]
311 result = None
313 for f in transform_functions:
314 for string, badchars, s in candidates[:]:
315 newstring, badchars, s = f(s, end)
316 string += newstring
317 if newstring and (result == None or len(string) < len(result)):
318 if nextchar not in badchars and s == end:
319 result = string
320 else:
321 candidates.append((string, badchars, s))
322 return result
324 def unparse_mirc(tagsandtext):
325 lasttags, lastchar = {}, ''
327 string = []
328 for tags, char in tagsandtext:
329 if tags != lasttags:
330 string.append(transform(lasttags, tags, char[0]))
331 string.append(char)
332 lasttags, lastchar = tags, char
333 return ''.join(string)
335 if __name__ == "__main__":
336 tests = [
337 'not\x02bold\x02not',
338 'not\x1Funderline\x1Fnot',
340 "\x02\x1FHi\x0F",
342 'not\x030,17white-on-black\x0304red-on-black\x03nothing',
344 "\x040000CC<\x04nick\x040000CC>\x04 text",
346 '\x04770077,FFFFFFbersirc color with background! \x04000077setting foreground! \x04reset!',
348 '\x047700,FFFFbersirc',
350 "\x03123Hello",
352 "\x0312,Hello",
354 "\x034Hello",
356 "Bo\x02ld",
358 "\x034,5Hello\x036Goodbye",
360 "\x04ff0000,00ff00Hello\x040000ffGoodbye",
362 "\x04777777(\x04\x0400CCCCstuff\x04\x04777777)\x04",
364 '\x0307orange\x04CCCCCCgrey\x0307orange',
366 '\x04CCCCCC,444444sdf\x0304jkl',
368 '\x0403\x02\x02,trixy',
370 '\x04FFFFFF\x02\x02,000000trixy for bersirc',
373 results = [
374 ([{'from': 3, 'data': ('weight', '\x02'), 'to': 7}], 'notboldnot'),
376 ([{'from': 3, 'data': ('underline', '\x1f'), 'to': 12}], 'notunderlinenot'),
378 ([{'from': 0, 'data': ('weight', '\x02'), 'to': 2}, {'from': 0, 'data': ('underline', '\x1f'), 'to': 2}], 'Hi'),
380 ([{'from': 3, 'data': ('foreground', '#FFFFFF'), 'to': 17}, {'from': 3, 'data': ('background', '#000000'), 'to': 17}, {'from': 17, 'data': ('foreground', '#FF0000'), 'to': 29}, {'from': 17, 'data': ('background', '#000000'), 'to': 29}], 'notwhite-on-blackred-on-blacknothing'),
382 ([{'from': 0, 'data': ('foreground', '#0000CC'), 'to': 1}, {'from': 5, 'data': ('foreground', '#0000CC'), 'to': 6}], '<nick> text'),
384 ([{'from': 0, 'data': ('foreground', '#770077'), 'to': 31}, {'from': 0, 'data': ('background', '#FFFFFF'), 'to': 31}, {'from': 31, 'data': ('foreground', '#000077'), 'to': 51}, {'from': 31, 'data': ('background', '#FFFFFF'), 'to': 51}], 'bersirc color with background! setting foreground! reset!'),
386 ([], '7700,FFFFbersirc'),
388 ([{'from': 0, 'data': ('foreground', '#0000FF'), 'to': 6}], '3Hello'),
390 ([{'from': 0, 'data': ('foreground', '#0000FF'), 'to': 6}], ',Hello'),
392 ([{'from': 0, 'data': ('foreground', '#FF0000'), 'to': 5}], 'Hello'),
394 ([{'from': 2, 'data': ('weight', '\x02'), 'to': 4}], 'Bold'),
396 ([{'from': 0, 'data': ('foreground', '#FF0000'), 'to': 5}, {'from': 0, 'data': ('background', '#7F0000'), 'to': 5}, {'from': 5, 'data': ('foreground', '#9C009C'), 'to': 12}, {'from': 5, 'data': ('background', '#7F0000'), 'to': 12}], 'HelloGoodbye'),
398 ([{'from': 0, 'data': ('foreground', '#FF0000'), 'to': 5}, {'from': 0, 'data': ('background', '#00FF00'), 'to': 5}, {'from': 5, 'data': ('foreground', '#0000FF'), 'to': 12}, {'from': 5, 'data': ('background', '#00FF00'), 'to': 12}], 'HelloGoodbye'),
400 ([{'from': 0, 'data': ('foreground', '#777777'), 'to': 1}, {'from': 1, 'data': ('foreground', '#00CCCC'), 'to': 6}, {'from': 6, 'data': ('foreground', '#777777'), 'to': 7}], '(stuff)'),
402 ([{'from': 0, 'data': ('foreground', '#FF7F00'), 'to': 6}, {'from': 6, 'data': ('foreground', '#CCCCCC'), 'to': 10}, {'from': 10, 'data': ('foreground', '#FF7F00'), 'to': 16}], 'orangegreyorange'),
404 ([{'from': 0, 'data': ('foreground', '#CCCCCC'), 'to': 3}, {'from': 0, 'data': ('background', '#444444'), 'to': 3}, {'from': 3, 'data': ('foreground', '#FF0000'), 'to': 6}, {'from': 3, 'data': ('background', '#444444'), 'to': 6}], 'sdfjkl'),
406 ([{'from': 2, 'data': ('weight', '\x02'), 'to': 2}], '03,trixy'),
408 ([{'from': 0, 'data': ('weight', '\x02'), 'to': 0}, {'from': 0, 'data': ('foreground', '#FFFFFF'), 'to': 24}], ',000000trixy for bersirc'),
411 #"""
413 #r = range(20000)
414 #for i in r:
415 # for test in tests:
416 # parse_mirc(test)
420 lines = [eval(line.strip()) for line in file("parse_mirc_torture_test.txt")]
422 for r in range(100):
423 for line in lines:
424 parse_mirc(line)
426 #"""
428 def setify_tags(tags):
429 return set(frozenset(tag.iteritems()) for tag in tags if tag['from'] != tag['to'])
431 def parsed_eq((tags1, text1), (tags2, text2)):
432 return setify_tags(tags1) == setify_tags(tags2) and text1 == text2
434 def parsed_to_unparsed((tags, text)):
435 result = []
436 for i, char in enumerate(text):
437 result.append((
438 dict(tag['data'] for tag in tags if tag['from'] <= i < tag['to']),
439 char))
440 return result
442 for i, (test, result) in enumerate(zip(tests, results)):
443 if not parsed_eq(parse_mirc(test), result):
444 print "parse_mirc failed test %s:" % i
445 print repr(test)
446 print parse_mirc(test)
447 print result
448 print
450 elif not parsed_eq(parse_mirc(unparse_mirc(parsed_to_unparsed(result))), result):
451 print "unparse_mirc failed test %s:" % i
452 print repr(test)
453 print unparse_mirc(test)
454 print
456 #import dis
457 #dis.dis(parse_mirc)