]>
Commit | Line | Data |
---|---|---|
1 | # -*- coding: utf-8 -*- | |
2 | # | |
3 | # color.py - remove/replace colors in WeeChat strings | |
4 | # | |
5 | # Copyright (C) 2011-2016 Sébastien Helleu <flashcode@flashtux.org> | |
6 | # | |
7 | # This file is part of QWeeChat, a Qt remote GUI for WeeChat. | |
8 | # | |
9 | # QWeeChat is free software; you can redistribute it and/or modify | |
10 | # it under the terms of the GNU General Public License as published by | |
11 | # the Free Software Foundation; either version 3 of the License, or | |
12 | # (at your option) any later version. | |
13 | # | |
14 | # QWeeChat is distributed in the hope that it will be useful, | |
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | # GNU General Public License for more details. | |
18 | # | |
19 | # You should have received a copy of the GNU General Public License | |
20 | # along with QWeeChat. If not, see <http://www.gnu.org/licenses/>. | |
21 | # | |
22 | ||
23 | import re | |
24 | ||
25 | RE_COLOR_ATTRS = r'[*!/_|]*' | |
26 | RE_COLOR_STD = r'(?:%s\d{2})' % RE_COLOR_ATTRS | |
27 | RE_COLOR_EXT = r'(?:@%s\d{5})' % RE_COLOR_ATTRS | |
28 | RE_COLOR_ANY = r'(?:%s|%s)' % (RE_COLOR_STD, RE_COLOR_EXT) | |
29 | # \x19: color code, \x1A: set attribute, \x1B: remove attribute, \x1C: reset | |
30 | RE_COLOR = re.compile( | |
31 | r'(\x19(?:\d{2}|F%s|B\d{2}|B@\d{5}|E|\\*%s(,%s)?|@\d{5}|b.|\x1C))|\x1A.|' | |
32 | r'\x1B.|\x1C' | |
33 | % (RE_COLOR_ANY, RE_COLOR_ANY, RE_COLOR_ANY)) | |
34 | ||
35 | TERMINAL_COLORS = \ | |
36 | '000000cd000000cd00cdcd000000cdcd00cd00cdcde5e5e5' \ | |
37 | '4d4d4dff000000ff00ffff000000ffff00ff00ffffffffff' \ | |
38 | '00000000002a0000550000800000aa0000d4002a00002a2a' \ | |
39 | '002a55002a80002aaa002ad400550000552a005555005580' \ | |
40 | '0055aa0055d400800000802a0080550080800080aa0080d4' \ | |
41 | '00aa0000aa2a00aa5500aa8000aaaa00aad400d40000d42a' \ | |
42 | '00d45500d48000d4aa00d4d42a00002a002a2a00552a0080' \ | |
43 | '2a00aa2a00d42a2a002a2a2a2a2a552a2a802a2aaa2a2ad4' \ | |
44 | '2a55002a552a2a55552a55802a55aa2a55d42a80002a802a' \ | |
45 | '2a80552a80802a80aa2a80d42aaa002aaa2a2aaa552aaa80' \ | |
46 | '2aaaaa2aaad42ad4002ad42a2ad4552ad4802ad4aa2ad4d4' \ | |
47 | '55000055002a5500555500805500aa5500d4552a00552a2a' \ | |
48 | '552a55552a80552aaa552ad455550055552a555555555580' \ | |
49 | '5555aa5555d455800055802a5580555580805580aa5580d4' \ | |
50 | '55aa0055aa2a55aa5555aa8055aaaa55aad455d40055d42a' \ | |
51 | '55d45555d48055d4aa55d4d480000080002a800055800080' \ | |
52 | '8000aa8000d4802a00802a2a802a55802a80802aaa802ad4' \ | |
53 | '80550080552a8055558055808055aa8055d480800080802a' \ | |
54 | '8080558080808080aa8080d480aa0080aa2a80aa5580aa80' \ | |
55 | '80aaaa80aad480d40080d42a80d45580d48080d4aa80d4d4' \ | |
56 | 'aa0000aa002aaa0055aa0080aa00aaaa00d4aa2a00aa2a2a' \ | |
57 | 'aa2a55aa2a80aa2aaaaa2ad4aa5500aa552aaa5555aa5580' \ | |
58 | 'aa55aaaa55d4aa8000aa802aaa8055aa8080aa80aaaa80d4' \ | |
59 | 'aaaa00aaaa2aaaaa55aaaa80aaaaaaaaaad4aad400aad42a' \ | |
60 | 'aad455aad480aad4aaaad4d4d40000d4002ad40055d40080' \ | |
61 | 'd400aad400d4d42a00d42a2ad42a55d42a80d42aaad42ad4' \ | |
62 | 'd45500d4552ad45555d45580d455aad455d4d48000d4802a' \ | |
63 | 'd48055d48080d480aad480d4d4aa00d4aa2ad4aa55d4aa80' \ | |
64 | 'd4aaaad4aad4d4d400d4d42ad4d455d4d480d4d4aad4d4d4' \ | |
65 | '0808081212121c1c1c2626263030303a3a3a4444444e4e4e' \ | |
66 | '5858586262626c6c6c7676768080808a8a8a9494949e9e9e' \ | |
67 | 'a8a8a8b2b2b2bcbcbcc6c6c6d0d0d0dadadae4e4e4eeeeee' | |
68 | ||
69 | # WeeChat basic colors (color name, index in terminal colors) | |
70 | WEECHAT_BASIC_COLORS = ( | |
71 | ('default', 0), ('black', 0), ('darkgray', 8), ('red', 1), | |
72 | ('lightred', 9), ('green', 2), ('lightgreen', 10), ('brown', 3), | |
73 | ('yellow', 11), ('blue', 4), ('lightblue', 12), ('magenta', 5), | |
74 | ('lightmagenta', 13), ('cyan', 6), ('lightcyan', 14), ('gray', 7), | |
75 | ('white', 0)) | |
76 | ||
77 | ||
78 | class Color(): | |
79 | def __init__(self, color_options, debug=False): | |
80 | self.color_options = color_options | |
81 | self.debug = debug | |
82 | ||
83 | def _rgb_color(self, index): | |
84 | color = TERMINAL_COLORS[index*6:(index*6)+6] | |
85 | r = int(color[0:2], 16) * 0.85 | |
86 | g = int(color[2:4], 16) * 0.85 | |
87 | b = int(color[4:6], 16) * 0.85 | |
88 | return '%02x%02x%02x' % (r, g, b) | |
89 | ||
90 | def _convert_weechat_color(self, color): | |
91 | try: | |
92 | index = int(color) | |
93 | return '\x01(Fr%s)' % self.color_options[index] | |
94 | except: | |
95 | print('Error decoding WeeChat color "%s"' % color) | |
96 | return '' | |
97 | ||
98 | def _convert_terminal_color(self, fg_bg, attrs, color): | |
99 | try: | |
100 | index = int(color) | |
101 | return '\x01(%s%s#%s)' % (fg_bg, attrs, self._rgb_color(index)) | |
102 | except: | |
103 | print('Error decoding terminal color "%s"' % color) | |
104 | return '' | |
105 | ||
106 | def _convert_color_attr(self, fg_bg, color): | |
107 | extended = False | |
108 | if color[0].startswith('@'): | |
109 | extended = True | |
110 | color = color[1:] | |
111 | attrs = '' | |
112 | # keep_attrs = False | |
113 | while color.startswith(('*', '!', '/', '_', '|')): | |
114 | # TODO: manage the "keep attributes" flag | |
115 | # if color[0] == '|': | |
116 | # keep_attrs = True | |
117 | attrs += color[0] | |
118 | color = color[1:] | |
119 | if extended: | |
120 | return self._convert_terminal_color(fg_bg, attrs, color) | |
121 | try: | |
122 | index = int(color) | |
123 | return self._convert_terminal_color(fg_bg, attrs, | |
124 | WEECHAT_BASIC_COLORS[index][1]) | |
125 | except: | |
126 | print('Error decoding color "%s"' % color) | |
127 | return '' | |
128 | ||
129 | def _attrcode_to_char(self, code): | |
130 | codes = { | |
131 | '\x01': '*', | |
132 | '\x02': '!', | |
133 | '\x03': '/', | |
134 | '\x04': '_', | |
135 | } | |
136 | return codes.get(code, '') | |
137 | ||
138 | def _convert_color(self, match): | |
139 | color = match.group(0) | |
140 | if color[0] == '\x19': | |
141 | if color[1] == 'b': | |
142 | # bar code, ignored | |
143 | return '' | |
144 | elif color[1] == '\x1C': | |
145 | # reset | |
146 | return '\x01(Fr)\x01(Br)' | |
147 | elif color[1] in ('F', 'B'): | |
148 | # foreground or background | |
149 | return self._convert_color_attr(color[1], color[2:]) | |
150 | elif color[1] == '*': | |
151 | # foreground with optional background | |
152 | items = color[2:].split(',') | |
153 | s = self._convert_color_attr('F', items[0]) | |
154 | if len(items) > 1: | |
155 | s += self._convert_color_attr('B', items[1]) | |
156 | return s | |
157 | elif color[1] == '@': | |
158 | # direct ncurses pair number, ignored | |
159 | return '' | |
160 | elif color[1] == 'E': | |
161 | # text emphasis, ignored | |
162 | return '' | |
163 | if color[1:].isdigit(): | |
164 | return self._convert_weechat_color(int(color[1:])) | |
165 | # color code | |
166 | pass | |
167 | elif color[0] == '\x1A': | |
168 | # set attribute | |
169 | return '\x01(+%s)' % self._attrcode_to_char(color[1]) | |
170 | elif color[0] == '\x1B': | |
171 | # remove attribute | |
172 | return '\x01(-%s)' % self._attrcode_to_char(color[1]) | |
173 | elif color[0] == '\x1C': | |
174 | # reset | |
175 | return '\x01(Fr)\x01(Br)' | |
176 | # should never be executed! | |
177 | return match.group(0) | |
178 | ||
179 | def _convert_color_debug(self, match): | |
180 | group = match.group(0) | |
181 | for code in (0x01, 0x02, 0x03, 0x04, 0x19, 0x1A, 0x1B): | |
182 | group = group.replace(chr(code), '<x%02X>' % code) | |
183 | return group | |
184 | ||
185 | def convert(self, text): | |
186 | if not text: | |
187 | return '' | |
188 | if self.debug: | |
189 | return RE_COLOR.sub(self._convert_color_debug, text) | |
190 | else: | |
191 | return RE_COLOR.sub(self._convert_color, text) | |
192 | ||
193 | ||
194 | def remove(text): | |
195 | """Remove colors in a WeeChat string.""" | |
196 | if not text: | |
197 | return '' | |
198 | return re.sub(RE_COLOR, '', text) |