From: Christian Heller Date: Tue, 23 Sep 2025 15:02:01 +0000 (+0200) Subject: Use exception to communicate 401 from Client to TUI, move LogScope into client_tui.py. X-Git-Url: https://plomlompom.com/repos/?a=commitdiff_plain;h=67d36da4dcf8215716b3dc1bdb1d8294b60013c3;p=ircplom Use exception to communicate 401 from Client to TUI, move LogScope into client_tui.py. --- diff --git a/ircplom/client.py b/ircplom/client.py index d48e37e..3eb934d 100644 --- a/ircplom/client.py +++ b/ircplom/client.py @@ -3,7 +3,6 @@ from abc import ABC, abstractmethod from base64 import b64encode from dataclasses import dataclass, InitVar -from enum import Enum, auto from getpass import getuser from threading import Thread from typing import (Any, Callable, Collection, Generic, Iterable, Iterator, @@ -24,6 +23,10 @@ class SendFail(BaseException): 'When Client.send fails.' +class TargetUserOffline(BaseException): + 'When according to server our target user is not to be found.' + + class ImplementationFail(BaseException): 'When no matching parser found for server message.' @@ -341,16 +344,6 @@ class Channel: exits: Dict[str] -class LogScope(Enum): - 'Where log messages should go.' - ALL = auto() - DEBUG = auto() - RAW = auto() - CHAT = auto() - USER = auto() - USER_NO_CHANNELS = auto() - - @dataclass class _ClientIdMixin: 'Collects a Client\'s ID at .client_id.' @@ -869,8 +862,7 @@ class Client(ABC, ClientQueueMixin): pass @abstractmethod - def _log(self, msg: str, scope: Optional[LogScope] = None, **kwargs - ) -> None: + def _log(self, msg: str, **kwargs) -> None: pass def send(self, verb: str, *args) -> IrcMessage: @@ -927,8 +919,7 @@ class Client(ABC, ClientQueueMixin): elif ret['_verb'] == '372': # RPL_MOTD self.db.motd.append(ret['line']) elif ret['_verb'] == '401': # ERR_NOSUCHNICK - self._log(f'{ret["missing"]} not online', scope=LogScope.CHAT, - log_target=ret['missing'], alert=True) + raise TargetUserOffline(ret['missing']) elif ret['_verb'] == '432': # ERR_ERRONEOUSNICKNAME alert = 'nickname refused for bad format' if 'nick' not in ret: diff --git a/ircplom/client_tui.py b/ircplom/client_tui.py index b9f00ae..e7adda2 100644 --- a/ircplom/client_tui.py +++ b/ircplom/client_tui.py @@ -1,14 +1,15 @@ 'TUI adaptions to Client.' # built-ins +from enum import Enum, auto from getpass import getuser from typing import Any, Callable, Optional, Sequence # ourselves from ircplom.tui_base import (BaseTui, PromptWidget, TuiEvent, Window, CMD_SHORTCUTS) from ircplom.client import ( - AutoAttrMixin, Channel, ChatMessage, Client, ClientQueueMixin, Dict, - DictItem, ImplementationFail, IrcConnSetup, LogScope, NewClientEvent, - NickUserHost, SendFail, ServerCapability, SharedClientDbFields, User) + AutoAttrMixin, Channel, ChatMessage, Client, ClientQueueMixin, Dict, + DictItem, ImplementationFail, IrcConnSetup, NewClientEvent, NickUserHost, + SendFail, ServerCapability, SharedClientDbFields, TargetUserOffline, User) from ircplom.irc_conn import IrcMessage CMD_SHORTCUTS['disconnect'] = 'window.disconnect' @@ -24,21 +25,31 @@ _LOG_PREFIX_OUT = '>' _LOG_PREFIX_IN = '<' +class _LogScope(Enum): + 'Where log messages should go.' + ALL = auto() + DEBUG = auto() + RAW = auto() + CHAT = auto() + USER = auto() + USER_NO_CHANNELS = auto() + + class _ClientWindow(Window, ClientQueueMixin): - def __init__(self, scope: LogScope, log: Callable, **kwargs) -> None: + def __init__(self, scope: _LogScope, log: Callable, **kwargs) -> None: self.scope = scope self._log = log super().__init__(**kwargs) self._title = f'{self.client_id} '\ - + f':{"DEBUG" if self.scope == LogScope.DEBUG else "RAW"}' + + f':{"DEBUG" if self.scope == _LogScope.DEBUG else "RAW"}' def _send_msg(self, verb: str, params: tuple[str, ...]) -> None: self._client_trigger('send_w_params_tuple', verb=verb, params=params) def cmd__disconnect(self, quit_msg: str = 'ircplom says bye') -> None: 'Send QUIT command to server.' - self._log('requesting disconnect …', scope=LogScope.DEBUG) + self._log('requesting disconnect …', scope=_LogScope.DEBUG) self._send_msg('QUIT', (quit_msg,)) def cmd__reconnect(self) -> None: @@ -123,7 +134,7 @@ class _QueryWindow(_ChatWindow): class _Update: old_value: Any - results: list[tuple[LogScope, Any]] + results: list[tuple[_LogScope, Any]] def __init__(self, path: tuple[str, ...], value: Any) -> None: self.full_path = path @@ -166,7 +177,7 @@ class _UpdatingNode(AutoAttrMixin): self._set(update.key, update.value) do_report |= True if do_report: - update.results += [(LogScope.DEBUG, + update.results += [(_LogScope.DEBUG, tuple(sorted(update.value)) if isinstance(update.value, set) else update.value)] @@ -213,22 +224,22 @@ class _UpdatingChannel(_UpdatingNode, Channel): super().recursive_set_and_report_change(update) if update.key == 'topic': msg = f':{self.topic.who} set topic: {self.topic.what}' - update.results += [(LogScope.CHAT, [msg])] + update.results += [(_LogScope.CHAT, [msg])] elif update.key == 'user_ids': if not update.old_value: nicks = [] for id_ in sorted(update.value): nicks += [f'NICK:{id_}', ':, '] nicks.pop() - update.results += [(LogScope.CHAT, [':residents: '] + nicks)] + update.results += [(_LogScope.CHAT, [':residents: '] + nicks)] else: for id_ in (id_ for id_ in update.value if id_ not in update.old_value): - update.results += [(LogScope.CHAT, + update.results += [(_LogScope.CHAT, [f'NUH:{id_}', ': joins'])] for id_ in (id_ for id_ in update.old_value if id_ not in update.value): - update.results += [(LogScope.CHAT, + update.results += [(_LogScope.CHAT, _UpdatingUser.exit_msg_toks( f'NUH:{id_}', self.exits[id_]))] @@ -253,11 +264,11 @@ class _UpdatingUser(_UpdatingNode, User): self.prev_nick = update.old_value if update.old_value != '?': update.results += [ - (LogScope.USER, + (_LogScope.USER, [f':{self.prev} renames {update.value}'])] elif update.key == 'exit_msg': if update.value: - update.results += [(LogScope.USER_NO_CHANNELS, + update.results += [(_LogScope.USER_NO_CHANNELS, self.exit_msg_toks( f':{self}', update.value))] @@ -282,12 +293,12 @@ class _TuiClientDb(_UpdatingNode, SharedClientDbFields): super().recursive_set_and_report_change(update) if update.key == 'connection_state': if update.value == 'connected': - update.results += [(LogScope.ALL, [':CONNECTED'])] + update.results += [(_LogScope.ALL, [':CONNECTED'])] elif not update.value: - update.results += [(LogScope.ALL, [':DISCONNECTED'])] + update.results += [(_LogScope.ALL, [':DISCONNECTED'])] elif update.key == 'message' and update.value: assert isinstance(update.value, ChatMessage) - update.results += [(LogScope.CHAT, [':' + update.value.content])] + update.results += [(_LogScope.CHAT, [':' + update.value.content])] class _ClientWindowsManager: @@ -297,12 +308,12 @@ class _ClientWindowsManager: self._tui_new_window = tui_new_window self.db = _TuiClientDb() self.windows: list[_ClientWindow] = [] - for scope in (LogScope.DEBUG, LogScope.RAW): + for scope in (_LogScope.DEBUG, _LogScope.RAW): self._new_win(scope) - def _new_win(self, scope: LogScope, chatname: str = '') -> _ClientWindow: + def _new_win(self, scope: _LogScope, chatname: str = '') -> _ClientWindow: kwargs = {'scope': scope, 'log': self.log, 'win_cls': _ClientWindow} - if scope == LogScope.CHAT: + if scope == _LogScope.CHAT: kwargs['win_cls'] = ( _ChannelWindow if self.db.is_chan_name(chatname) else _QueryWindow) @@ -314,10 +325,10 @@ class _ClientWindowsManager: self.windows += [win] return win - def window(self, scope: LogScope, chatname: str = '') -> _ClientWindow: + def window(self, scope: _LogScope, chatname: str = '') -> _ClientWindow: 'Return client window of scope.' for win in [w for w in self.windows if w.scope == scope]: - if scope == LogScope.CHAT: + if scope == _LogScope.CHAT: if isinstance(win, _ChatWindow) and win.chatname == chatname: return win continue @@ -338,14 +349,14 @@ class _ClientWindowsManager: self.db.users[user_id].nick, self.db.users[user_id].prev_nick})))] - def log(self, msg: str, scope: LogScope, **kwargs) -> None: + def log(self, msg: str, scope: _LogScope, **kwargs) -> None: 'From parsing scope, kwargs, build prefix before sending to logger.' first_char = '$' sender_label = '' receiving: Optional[bool] = None - if scope == LogScope.RAW: + if scope == _LogScope.RAW: receiving = not kwargs.get('out', False) - if scope == LogScope.CHAT and 'sender' in kwargs: + if scope == _LogScope.CHAT and 'sender' in kwargs: receiving = bool(kwargs['sender']) sender_label = ' [' + (kwargs['sender'] if receiving else self.db.users['me'].nick) + ']' @@ -364,8 +375,8 @@ class _ClientWindowsManager: for scope, result in update.results: log_kwargs: dict[str, Any] = {'scope': scope} - if scope in {LogScope.CHAT, LogScope.USER, - LogScope.USER_NO_CHANNELS}: + if scope in {_LogScope.CHAT, _LogScope.USER, + _LogScope.USER_NO_CHANNELS}: if update.full_path == ('message',): log_kwargs['log_target'] = (update.value.target or update.value.sender) @@ -412,16 +423,17 @@ class ClientTui(BaseTui): def _log_target_wins(self, **kwargs) -> Sequence[Window]: if (scope := kwargs.get('scope', None)): m = self._client_mngrs[kwargs['client_id']] - if scope == LogScope.ALL: + if scope == _LogScope.ALL: return [w for w in m.windows - if w.scope not in {LogScope.DEBUG, LogScope.RAW}] - if scope == LogScope.DEBUG: - return [m.window(LogScope.DEBUG), m.window(LogScope.RAW)] - if scope == LogScope.CHAT: - return [m.window(LogScope.CHAT, chatname=kwargs['log_target'])] - if scope == LogScope.USER: + if w.scope not in {_LogScope.DEBUG, _LogScope.RAW}] + if scope == _LogScope.DEBUG: + return [m.window(_LogScope.DEBUG), m.window(_LogScope.RAW)] + if scope == _LogScope.CHAT: + return [m.window(_LogScope.CHAT, + chatname=kwargs['log_target'])] + if scope == _LogScope.USER: return m.windows_for_userid(kwargs['log_target']) - if scope == LogScope.USER_NO_CHANNELS: + if scope == _LogScope.USER_NO_CHANNELS: return m.windows_for_userid(kwargs['log_target'], w_channels=False) return [m.window(scope)] @@ -514,22 +526,26 @@ class ClientKnowingTui(Client): def send(self, verb: str, *args) -> IrcMessage: msg = super().send(verb, *args) - self._log(msg.raw, scope=LogScope.RAW, out=True) + self._log(msg.raw, scope=_LogScope.RAW, out=True) return msg def handle_msg(self, msg: IrcMessage) -> None: - self._log(msg.raw, scope=LogScope.RAW, out=False) + self._log(msg.raw, scope=_LogScope.RAW, out=False) try: super().handle_msg(msg) except ImplementationFail as e: self._log(str(e), alert=True) + except TargetUserOffline as e: + name = f'{e}' + self._log(f'{name} not online', scope=_LogScope.CHAT, + log_target=name, alert=True) - def _log(self, msg: str, scope: Optional[LogScope] = None, **kwargs + def _log(self, msg: str, scope: Optional[_LogScope] = None, **kwargs ) -> None: if not scope: - scope = LogScope.DEBUG + scope = _LogScope.DEBUG self._client_tui_trigger('log', scope=scope, msg=msg, **kwargs) - if scope == LogScope.RAW: + if scope == _LogScope.RAW: with open(f'{self.client_id}.log', 'a', encoding='utf8') as f: f.write(('>' if kwargs['out'] else '<') + f' {msg}\n')