from typing import Optional
from uuid import UUID, uuid4
# ourselves
-from ircplom.events import (AffectiveEvent, ExceptionEvent, PayloadMixin,
- QueueMixin)
+from ircplom.events import (AffectiveEvent, ExceptionEvent, Logger,
+ PayloadMixin, QueueMixin)
from ircplom.irc_conn import (BaseIrcConnection, IrcConnAbortException,
IrcMessage)
ClientsDb = dict[UUID, 'Client']
CHAT_GLOB = '*'
+_LOG_PREFIX_PRIVMSG = ''
+_LOG_PREFIX_SEND_FMT = '> '
+_LOG_PREFIX_SEND_RAW = '=>|'
+_LOG_PREFIX_RECV_RAW = '<-|'
+
@dataclass
class ClientIdMixin:
self._cap_neg_states: dict[str, bool] = {}
self.caps: dict[str, _ServerCapability] = {}
self.id_ = uuid4()
+ self.log = Logger(self._log)
self.update_login(nick_confirmed=False,
nickname=self.conn_setup.nickname)
self.start_connecting()
client_id=self.id_)
self._cput(ClientEvent.make_subtype('on_connect'))
except IrcConnAbortException as e:
- self.log(f'# ALERT: {e}')
+ self.log.alert(str(e))
except Exception as e: # pylint: disable=broad-exception-caught
self._put(ExceptionEvent(e))
def on_connect(self) -> None:
'Steps to perform right after connection.'
- self.log(msg='# connected to server', chat=CHAT_GLOB)
+ self.log.add(msg='connected to server', chat=CHAT_GLOB)
self.try_send_cap('LS', ('302',))
self.send(IrcMessage(verb='USER',
params=(getuser(), '0', '*',
self.cap_neg_set(verb, done=True)
@abstractmethod
- def log(self, msg: str, chat: str = '') -> None:
+ def _log(self, msg: str, chat: str = '') -> None:
'''Write msg into log of chat, whatever shape that may have.
Messages to chat=CHAT_GLOB are meant to appear in all widgets mapped to
def send(self, msg: IrcMessage, chat: str = '') -> None:
'Send line-separator-delimited message over socket.'
if not self.conn:
- self.log('# ALERT: cannot send, connection seems closed')
+ self.log.alert('cannot send, connection seems closed')
return
self.conn.send(msg)
- self.log(msg=f'> {msg.raw}', chat=chat)
- self.log(msg=f'=>| {msg.raw}', chat=':raw')
+ self.log.add(msg.raw, prefix=_LOG_PREFIX_SEND_FMT, chat=chat)
+ self.log.add(msg.raw, prefix=_LOG_PREFIX_SEND_RAW, chat=':raw')
def update_login(self, nick_confirmed: bool, nickname: str = '') -> None:
'''Manage conn_setup..nickname, .nick_confirmed.
(Useful for subclass extension.)
'''
first_run = not hasattr(self.conn_setup, 'nickname')
- prefix = '# nickname'
+ prefix = 'nickname'
if first_run or (nickname and nickname != self.conn_setup.nickname):
verb = ('set' if first_run
else f'changed from "{self.conn_setup.nickname}"')
self.conn_setup.nickname = nickname
- self.log(msg=f'{prefix} {verb} to "{nickname}"', chat=CHAT_GLOB)
+ self.log.add(f'{prefix} {verb} to "{nickname}"', chat=CHAT_GLOB)
if first_run or nick_confirmed != self.nick_confirmed:
self.nick_confirmed = nick_confirmed
if not first_run:
- self.log(f'{prefix} {"" if nick_confirmed else "un"}confirmed')
+ self.log.add(f'{prefix} {"" if nick_confirmed else "un"}'
+ 'confirmed')
def close(self) -> None:
'Close both recv Loop and socket.'
- self.log(msg='# disconnecting from server', chat=CHAT_GLOB)
+ self.log.add(msg='disconnecting from server', chat=CHAT_GLOB)
if self.conn:
self.conn.close()
self.conn = None
def handle_msg(self, msg: IrcMessage) -> None:
'Process incoming msg towards appropriate client steps.'
- self.log(f'<-| {msg.raw}', ':raw')
+ self.log.add(msg.raw, prefix=_LOG_PREFIX_RECV_RAW, chat=':raw')
match msg.verb:
case 'PING':
self.send(IrcMessage(verb='PONG', params=(msg.params[0],)))
case '001' | 'NICK':
self.update_login(nickname=msg.params[0], nick_confirmed=True)
case 'PRIVMSG':
- self.log(msg=str(msg.params), chat=msg.source)
+ self.log.add(msg=str(msg.params), prefix=_LOG_PREFIX_PRIVMSG,
+ chat=msg.source)
case 'CAP':
match msg.params[1]:
case 'LS' | 'LIST':
if self.cap_neg_done('LIST'):
self.try_send_cap('END')
if not self.cap_neg('printing'):
- self.log('# server capabilities (enabled: "+"):')
+ self.log.add('server capabilities (enabled: "+"):')
for cap_name, cap in self.caps.items():
- self.log('# ' + cap.str_for_log(cap_name))
+ self.log.add(cap.str_for_log(cap_name))
self.cap_neg_set('printing', done=True)
elif self.cap_neg_done('LS'):
for cap_name in ('server-time', 'account-tag', 'sasl'):
def affect(self, target: 'Client') -> None:
if target.conn:
- target.log('# ALERT: reconnection called, but still seem '
- 'connected, so nothing to do.')
+ target.log.alert('reconnection called, but still seem connected, '
+ 'so nothing to do.')
else:
target.start_connecting()
from dataclasses import dataclass
from queue import SimpleQueue, Empty as QueueEmpty
from threading import Thread
-from typing import Any, Iterator, Literal, Self
+from typing import Any, Callable, Iterator, Literal, Self
+
+_LOG_PREFIX_DEFAULT = '# '
+_LOG_PREFIX_ALERT = f'{_LOG_PREFIX_DEFAULT}ALERT: '
@dataclass
self._put(it_yield)
except Exception as e: # pylint: disable=broad-exception-caught
self._put(ExceptionEvent(e))
+
+
+class Logger:
+ 'Wrapper for logging tasks.'
+
+ def __init__(self, to_call: Callable[[str], None]) -> None:
+ self._to_call = to_call
+
+ def add(self, msg: str, prefix: str = _LOG_PREFIX_DEFAULT, **kwargs
+ ) -> None:
+ 'Add msg to log.'
+ self._to_call(f'{prefix}{msg}', **kwargs)
+
+ def alert(self, msg: str, **kwargs) -> None:
+ 'Add msg prefixed with _LOG_PREFIX_ALERT to log.'
+ self.add(msg, prefix=_LOG_PREFIX_ALERT, **kwargs)
from blessed import Terminal as BlessedTerminal
# ourselves
from ircplom.events import (
- AffectiveEvent, Loop, PayloadMixin, QueueMixin, QuitEvent)
+ AffectiveEvent, Logger, Loop, PayloadMixin, QueueMixin, QuitEvent)
# from ircplom.irc_conn import IrcMessage
_MIN_HEIGHT = 4
elif len(self.payload) == 1:
target.window.prompt.insert(self.payload)
else:
- target.log(f'# ALERT: unknown keyboard input: {self.payload}')
+ target.log.alert(f'unknown keyboard input: {self.payload}')
super().affect(target)
self.term = term
self._window_idx = 0
self.windows = [Window(idx=self._window_idx, term=self.term)]
+ self.log = Logger(
+ lambda msg: # pylint: disable=unnecessary-lambda # to keep …
+ self.window.log.append(msg)) # … up-to-date _what_ window's .log
self._put(_SetScreenEvent())
def cmd_name_to_cmd(self, cmd_name: str) -> Optional[Callable]:
'Currently selected Window.'
return self.windows[self._window_idx]
- def log(self, msg: str) -> None:
- 'Post msg to active window\'s log.'
- self.window.log.append(msg)
-
def _switch_window(self, idx: int) -> None:
self._window_idx = idx
self.window.draw()
else:
alert = 'not prefixed by /'
if alert:
- self.log(f'# ALERT: invalid prompt command: {alert}')
+ self.log.alert(f'invalid prompt command: {alert}')
def cmd__quit(self) -> None:
'Trigger program exit.'