1 # -*- encoding: utf-8 -*-
2 # Copyright © 2022 Alejandro R. Sedeño <asedeno@mit.edu>
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions
9 # 1. Redistributions of source code must retain the above copyright
10 # notice, this list of conditions and the following disclaimer.
12 # 2. Redistributions in binary form must reproduce the above
13 # copyright notice, this list of conditions and the following
14 # disclaimer in the documentation and/or other materials provided
15 # with the distribution.
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
18 # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
19 # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
20 # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
22 # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
24 # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
27 # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
28 # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
41 from .. import chunks, messages, roost, util, zcode
44 # I'm not sure why I can't just use the standard form of
45 # _enabled = util.Configurable(...) here, but this works.
50 'time': 'receiveTime',
51 'show_seconds': False,
53 'show_signatures': False,
59 def setter(_context, value):
64 'user.vt_decor.enabled',
66 action=_gen_setter('enabled'),
67 oneof=('True', 'False'),
68 validate=lambda val: isinstance(val, bool),
69 coerce=util.coerce_bool,
75 action=_gen_setter('time'),
76 oneof=('time', 'receiveTime'),
80 'user.vt_decor.show_seconds',
82 action=_gen_setter('show_seconds'),
83 oneof=('True', 'False'),
84 validate=lambda val: isinstance(val, bool),
85 coerce=util.coerce_bool,
89 'user.vt_decor.show_addr',
91 action=_gen_setter('show_addr'),
92 oneof=('True', 'False'),
93 validate=lambda val: isinstance(val, bool),
94 coerce=util.coerce_bool,
98 'user.vt_decor.show_signatures',
100 action=_gen_setter('show_signatures'),
101 oneof=('True', 'False'),
102 validate=lambda val: isinstance(val, bool),
103 coerce=util.coerce_bool,
115 except AttributeError:
118 frame = inspect.currentframe()
123 ui = frame.f_locals['self'].ui
126 except (KeyError, AttributeError):
134 MSG_NO_WRAP_PATTERNS = [re.compile(x, re.MULTILINE) for x in (
140 MSG_LIST_WRAP_PATTERN = re.compile(r'(^\s*[-*]\s+)', re.MULTILINE)
143 'ANDREW.CMU.EDU': 'AN',
148 class RoostVTDecor(roost.RoostMessage.Decor):
152 def decorate(cls, msg, decoration):
154 if SETTINGS['enabled'] and _get_width() >= SETTINGS['min_width']:
155 return cls.vt_decorate(msg, decoration)
158 return super().decorate(msg, decoration)
162 force_wrap_tuples = {
163 ('moira', 'incremental'),
164 ('scripts', 'nagios.multivalue-key.mysql-s'),
165 ('scripts', 'nagios.unique-key.mysql-s'),
169 (msg.data['classKey'], '*'),
170 (msg.data['classKey'], msg.data['instanceKey']),
173 return (bool(force_wrap_tuples & msg_tuples) or
174 not any(x.search(msg.body) for x in MSG_NO_WRAP_PATTERNS))
177 def list_rewrap_p(msg):
178 return len(MSG_LIST_WRAP_PATTERN.findall(msg.body)) >= 2
181 def partial_rewrap(cls, body, width, indent):
185 for m in MSG_LIST_WRAP_PATTERN.split(body):
188 if MSG_LIST_WRAP_PATTERN.match(m):
192 ret += m.lstrip('\n')
193 bindent = ' ' * util.glyphwidth(m)
196 # collapse whitespace and wrap.
198 # list appears to end and have further content.
199 # deal with final (?) bullet...
200 bm, rest = m.split('\n\n', 1)
201 ret += textwrap.fill(re.sub(r'\s+', ' ', bm), width,
202 initial_indent=indent+bindent,
203 subsequent_indent=indent+bindent,
204 break_long_words=False,
205 break_on_hyphens=False).lstrip()
207 # ... and then indent and wrap following content normally.
209 ret += textwrap.fill(re.sub(r'\s+', ' ', rest), width,
210 initial_indent=indent,
211 subsequent_indent=indent,
212 break_long_words=False,
213 break_on_hyphens=False)
216 ret += textwrap.fill(re.sub(r'\s+', ' ', m), width,
217 initial_indent=indent+bindent,
218 subsequent_indent=indent+bindent,
219 break_long_words=False,
220 break_on_hyphens=False).lstrip()
224 except Exception as e:
229 def vt_decorate(cls, msg, decoration):
232 tags = cls.decotags(decoration)
234 realm = msg.backend.realm
235 sender = msg.data['sender']
236 recipient = msg.data['recipient']
237 if msg.data['isPersonal'] and msg.data['isOutgoing']:
238 sender = f'➤{recipient}'
239 if sender.endswith(realm):
240 sender = sender[:sender.index('@')]
241 klass = msg.data['class'] if msg.data['classKey'] != 'message' else ''
242 inst = msg.data['instance'] or "''"
243 if klass and inst.lower() == 'personal':
246 dest = f'{klass}[{inst}]'
254 }[msg.data['opcode']]
258 '%H:%M:%S' if SETTINGS['show_seconds'] else '%H:%M',
259 time.localtime(msg.data[SETTINGS['time']] / 1000))
261 if recipient.startswith('@'):
262 mrealm = REALM_MAP.get(recipient[1:], '??')
263 dest = f'{mrealm} {dest}'
265 dest_width = 18 - (util.glyphwidth(dest) - len(dest))
267 prefix = f'{sender:10.10} {t} {dest:{dest_width}.{dest_width}} {auth} '
268 indent = ' ' * util.glyphwidth(prefix)
270 body = msg.body.rstrip()
271 if cls.list_rewrap_p(msg):
272 body = cls.partial_rewrap(body, width, indent)
273 elif cls.rewrap_p(msg):
274 body = textwrap.fill(body, width,
275 initial_indent=indent,
276 subsequent_indent=indent,
277 break_long_words=False,
278 break_on_hyphens=False).lstrip()
280 body = textwrap.indent(body, indent).lstrip()
282 # body = f'{list(decoration.keys())}\n'
283 if msg.backend.format_body == 'format':
284 cbody = zcode.tag(body, frozenset(tags))
285 elif msg.backend.format_body == 'clear':
286 cbody = chunks.Chunk([(tags, '')])
288 if msg.backend.format_body == 'strip':
289 body = zcode.strip(body)
290 cbody = chunks.Chunk([(tags, body)])
293 chunk = chunks.Chunk([(tags, f'{prefix}')]) + cbody
295 if SETTINGS['show_signatures'] and msg.backend.format_zsig != 'clear':
296 zsig = '\n' + textwrap.fill(
297 msg.data['signature'], width,
298 initial_indent=' -- ',
299 subsequent_indent=' ',
300 break_long_words=False,
301 break_on_hyphens=False)
303 if msg.backend.format_zsig == 'format':
304 chunk += zcode.tag(zsig, frozenset(tags))
305 elif msg.backend.format_zsig == 'strip':
306 chunk.append((tags, zcode.strip(zsig)))
308 chunk.append((tags, zsig))
310 if SETTINGS['show_addr']:
311 addr = str(ipaddress.ip_address(base64.b64decode(msg.data["uid"])[:4]))
312 # Hangs snipe while resolving; need to make async somehow.
313 #if addr not in HOSTNAMES:
315 # HOSTNAMES[addr] = socket.gethostbyaddr(addr)[0].upper()
317 # HOSTNAMES[addr] = addr
318 #addr = HOSTNAMES[addr]
319 chunk.append((tags | {'right'}, f'## {addr}'))
321 chunk.append((tags | {'right'}, ''))