From: Christian Heller Date: Sun, 23 Nov 2025 21:38:51 +0000 (+0100) Subject: Clean up message parsing code. X-Git-Url: https://plomlompom.com/repos/%7B%7Bprefix%7D%7D/move_up?a=commitdiff_plain;h=37c23520f61955b69168d5577ece25c5a1ce0157;p=ircplom Clean up message parsing code. --- diff --git a/src/ircplom/client.py b/src/ircplom/client.py index a273230..1c2444f 100644 --- a/src/ircplom/client.py +++ b/src/ircplom/client.py @@ -17,8 +17,9 @@ from ircplom.db_primitives import ( from ircplom.events import ( AffectiveEvent, CrashingException, ExceptionEvent, QueueMixin) from ircplom.irc_conn import ( - BaseIrcConnection, IrcConnException, IrcMessage, ERR_STR_TIMEOUT, - ILLEGAL_NICK_CHARS, ILLEGAL_NICK_FIRSTCHARS, ISUPPORT_DEFAULTS, PORT_SSL) + BaseIrcConnection, IrcConnException, IrcMessage, NickUserHost, + ERR_STR_TIMEOUT, ILLEGAL_NICK_CHARS, ILLEGAL_NICK_FIRSTCHARS, + ISUPPORT_DEFAULTS, PORT_SSL) from ircplom.msg_parse_expectations import MSG_EXPECTATIONS @@ -94,17 +95,6 @@ class SharedClientDbFields(IrcConnSetup): return name[0] in self.isupport['CHANTYPES'] -@dataclass -class NickUserHost: - 'Combination of nickname, username on host, and host.' - nick: str = '?' - user: str = '?' - host: str = '?' - - def __str__(self) -> str: - return f'{self.nick}!{self.user}@{self.host}' - - class User(NickUserHost): 'Adds to NickUserHost non-naming-specific attributes.' modes: str = '?' @@ -172,7 +162,7 @@ class _CompletableTopic(Completable, Topic): def complete(self) -> None: assert self.who is not None - copy = _NickUserHost.from_str(str(self.who)) + copy = NickUserHost.from_str(str(self.who)) self.completed = Topic(self.what, NickUserHost(copy.nick, copy.user, copy.host)) @@ -286,25 +276,6 @@ class _SetNickuserhostMixin: class _NickUserHost(NickUserHost): - @staticmethod - def possible_from(value: str) -> bool: - 'If class instance could be parsed from value.' - toks = value.split('!') - if not len(toks) == 2: - return False - toks = toks[1].split('@') - if not len(toks) == 2: - return False - return True - - @classmethod - def from_str(cls, value: str) -> Self: - 'Produce from string assumed to fit _!_@_ pattern.' - assert cls.possible_from(value) - toks = value.split('!') - toks = toks[0:1] + toks[1].split('@') - return cls(*toks) - @property def incremented(self) -> str: 'Return .nick with number suffix incremented, or "0" if none.' @@ -837,27 +808,22 @@ class Client(ABC, ClientQueueMixin): def handle_msg(self, msg: IrcMessage) -> None: 'Log msg.raw, then process incoming msg into appropriate client steps.' - ret = {} + ret: Optional[dict[str, Any]] = None for ex in [ex for ex in MSG_EXPECTATIONS if ex.verb == msg.verb]: - result = ex.parse_msg( - msg=msg, - is_chan_name=self.db.is_chan_name, - is_nick=self.db.is_nick, - possible_nickuserhost=_NickUserHost.possible_from, - into_nickuserhost=_NickUserHost.from_str) - if result is not None: - ret = result + ret = ex.parse_msg(msg, self.db.is_chan_name, self.db.is_nick) + if ret is not None: break - if '_verb' not in ret: + if ret is None: raise ImplementationFail(msg.raw) for n_u_h in ret['_nickuserhosts']: # update, turn into proper users if ret['_verb'] == 'QUIT' and 'me' not in self.db.users.keys(): # a QUIT before server gave us names should refer to us (and # we need a "me" user to consequently call its .quit) self.db.users['me'].nickuserhost = n_u_h - if (id_ := self.db.users.id_for_nickuserhost( - n_u_h, allow_none=True, updating=True)): - for ret_name in [k for k in ret if ret[k] is n_u_h]: + if (id_ := self.db.users.id_for_nickuserhost(n_u_h, + allow_none=True, + updating=True)): + for ret_name in [k for k in ret if ret[k] == n_u_h]: ret[ret_name] = self.db.users[id_] for verb in ('setattr', 'do', 'doafter'): for task, tok_names in [t for t in ret['_tasks'].items() diff --git a/src/ircplom/client_tui.py b/src/ircplom/client_tui.py index 63e0779..04e90a2 100644 --- a/src/ircplom/client_tui.py +++ b/src/ircplom/client_tui.py @@ -7,10 +7,10 @@ from typing import Any, Callable, Optional, Sequence # ourselves from ircplom.client import ( Channel, ChatMessage, Client, ClientQueueMixin, ImplementationFail, - IrcConnSetup, NewClientEvent, NickUserHost, SendFail, - ServerCapability, SharedClientDbFields, TargetUserOffline, User) + IrcConnSetup, NewClientEvent, SendFail, ServerCapability, + SharedClientDbFields, TargetUserOffline, User) from ircplom.db_primitives import AutoAttrMixin, Dict, DictItem -from ircplom.irc_conn import IrcMessage +from ircplom.irc_conn import IrcMessage, NickUserHost from ircplom.tui_base import ( BaseTui, FormattingString, PromptWidget, TuiEvent, Window, CMD_SHORTCUTS, LOG_FMT_ATTRS, LOG_FMT_TAG_ALERT, LOG_PREFIX_DEFAULT) diff --git a/src/ircplom/irc_conn.py b/src/ircplom/irc_conn.py index 3e15e04..21a74e4 100644 --- a/src/ircplom/irc_conn.py +++ b/src/ircplom/irc_conn.py @@ -1,6 +1,7 @@ -'Low-level IRC protocol / server connection management.' +'Low-level IRC protocol / server connection management, data primitives.' # built-ins from abc import ABC, abstractmethod +from dataclasses import dataclass from socket import EAI_AGAIN, EBADF, socket, gaierror as socket_gaierror from ssl import create_default_context as create_ssl_context from datetime import datetime @@ -32,6 +33,36 @@ _IRCSPEC_TAG_ESCAPES = ((r'\:', ';'), (r'\\', '\\')) +@dataclass +class NickUserHost: + 'Combination of nickname, username on host, and host.' + nick: str = '?' + user: str = '?' + host: str = '?' + + def __str__(self) -> str: + return f'{self.nick}!{self.user}@{self.host}' + + @staticmethod + def possible_from(value: str) -> bool: + 'If class instance could be parsed from value.' + toks = value.split('!') + if not len(toks) == 2: + return False + toks = toks[1].split('@') + if not len(toks) == 2: + return False + return True + + @classmethod + def from_str(cls, value: str) -> Self: + 'Produce from string assumed to fit _!_@_ pattern.' + assert cls.possible_from(value) + toks = value.split('!') + toks = toks[0:1] + toks[1].split('@') + return cls(*toks) + + class IrcMessage: 'Properly structured representation of IRC message as per IRCv3 spec.' _raw: Optional[str] = None diff --git a/src/ircplom/msg_parse_expectations.py b/src/ircplom/msg_parse_expectations.py index e01da27..5b622e9 100644 --- a/src/ircplom/msg_parse_expectations.py +++ b/src/ircplom/msg_parse_expectations.py @@ -1,10 +1,13 @@ 'Structured expectations and processing hints for server messages.' from enum import Enum, auto from typing import Any, Callable, NamedTuple, Optional, Self -from ircplom.irc_conn import IrcMessage +from ircplom.irc_conn import IrcMessage, NickUserHost -class _MsgTok(Enum): +_TOK_SKIPNUH = 'SKIPNUH' + + +class _MsgToken(Enum): 'Server message token classifications.' ANY = auto() CHANNEL = auto() @@ -15,7 +18,9 @@ class _MsgTok(Enum): NICK_USER_HOST = auto() -_MsgTokGuide = str | _MsgTok | tuple[str | _MsgTok, str] +_MsgTokenWithStr = _MsgToken | str +_MsgTokenGuide = _MsgTokenWithStr | tuple[_MsgTokenWithStr, str] +_MsgTokenValidatorDict = dict[_MsgTokenWithStr, Callable[[Any], bool]] class _Command(NamedTuple): @@ -30,112 +35,122 @@ class _Command(NamedTuple): if path_str)) -class _MsgParseExpectation: +class _Code(NamedTuple): + tok_name: str = '' + commands: tuple[_Command, ...] = tuple() + skip_nuh: bool = False - def __init__(self, - verb: str, - source: _MsgTokGuide, - params: tuple[_MsgTokGuide, ...] = tuple(), - bonus_tasks: tuple[str, ...] = tuple() - ) -> None: + def __bool__(self) -> bool: + return bool(self.tok_name) or bool(self.commands) - class _Code(NamedTuple): - title: str - commands: tuple[_Command, ...] + @classmethod + def from_(cls, input_: str) -> Self: + 'Split by ":" into commands (further split by ","), tok_name.' + commands_str, tok_name = input_.split(':', maxsplit=1) + skip_nuh = False + commands: list[_Command] = [] + for command_str in commands_str.split(','): + if command_str == _TOK_SKIPNUH: + skip_nuh = True + elif command_str: + commands += [_Command.from_(command_str)] + return cls(tok_name, tuple(commands), skip_nuh) + + +class _TokenExpectation(NamedTuple): + type_: _MsgTokenWithStr + code: _Code - @classmethod - def from_(cls, input_: str) -> Self: - 'Split by ":" into commands (further split by ","), title.' - cmdsstr, title = input_.split(':', maxsplit=1) - return cls(title, tuple(_Command.from_(t) - for t in cmdsstr.split(',') if t)) + @classmethod + def from_(cls, val: _MsgTokenGuide) -> Self: + 'Standardize value into .type_, (potentially empty) .code.' + t = ((val[0], _Code.from_(val[1])) if isinstance(val, tuple) + else (val, _Code())) + return cls(*t) - class _TokExpectation(NamedTuple): - type_: _MsgTok | str - code: Optional[_Code] - @classmethod - def from_(cls, val: _MsgTokGuide) -> Self: - 'Standardize value into .type_, (potentially empty) .code.' - t = ((val[0], _Code.from_(val[1])) if isinstance(val, tuple) - else (val, None)) - return cls(*t) +class _MsgParseExpectation: + VALIDATORS: _MsgTokenValidatorDict = { + _MsgToken.NONE: lambda tok: tok == '', + _MsgToken.NICK_USER_HOST: NickUserHost.possible_from, + _MsgToken.SERVER: lambda tok: ('.' in tok + and not set('@!') & set(tok))} + PARSERS: dict[_MsgTokenWithStr, Callable] = { + _MsgToken.LIST: lambda tok: tuple(tok.split()), + _MsgToken.NICK_USER_HOST: NickUserHost.from_str} + def __init__(self, + verb: str, + source: _MsgTokenGuide, + params: tuple[_MsgTokenGuide, ...] = tuple(), + bonus_tasks: tuple[str, ...] = tuple() + ) -> None: self.verb = verb - self.source = _TokExpectation.from_(source) - self.params = tuple(_TokExpectation.from_(param) for param in params) + self.source = _TokenExpectation.from_(source) + self.params = tuple(_TokenExpectation.from_(param) for param in params) self.bonus_tasks = tuple(_Code.from_(item) for item in bonus_tasks) def parse_msg(self, msg: IrcMessage, is_chan_name: Callable, is_nick: Callable, - possible_nickuserhost: Callable, - into_nickuserhost: Callable ) -> Optional[dict[str, Any]]: 'Try parsing msg into informative result dictionary, or None on fail.' - cmp_params: list[str | tuple[str, ...]] = [] - idx_after = 0 - for idx, param in enumerate(self.params): - if param == _MsgTok.LIST or (isinstance(param, tuple) - and param[0] == _MsgTok.LIST): - idx_after = len(msg.params) + 1 - (len(self.params) - idx) - cmp_params += [' '.join(msg.params[idx:idx_after])] - cmp_params += list(msg.params[idx_after:]) - break - cmp_params += msg.params[idx:idx + 1] - if (not idx_after) and len(cmp_params) != len(msg.params): - return None - cmp_fields = tuple([msg.source] + cmp_params) - ex_fields = tuple([self.source] + list(self.params)) - if len(ex_fields) != len(cmp_fields): - return None - validators: dict[_MsgTok, Callable[[Any], bool]] = { - _MsgTok.NONE: lambda tok: tok == '', - _MsgTok.CHANNEL: is_chan_name, - _MsgTok.NICKNAME: is_nick, - _MsgTok.NICK_USER_HOST: possible_nickuserhost, - _MsgTok.SERVER: lambda tok: '.' in tok and not set('@!') & set(tok) - } - parsers: dict[_MsgTok, Callable[[Any], Any]] = { - _MsgTok.LIST: lambda tok: tuple(tok.split()), - _MsgTok.NICK_USER_HOST: into_nickuserhost - } - parsed: dict[str, str | tuple[str, ...]] = {} - singled_tasks: list[tuple[_Command, str]] = [] - nickuserhosts = [] - for ex_tok, cmp_tok in [(ex_tok, cmp_fields[idx]) - for idx, ex_tok in enumerate(ex_fields)]: - if isinstance(ex_tok.type_, str) and ex_tok.type_ != cmp_tok: - return None - if (not isinstance(ex_tok.type_, str))\ - and ex_tok.type_ in validators\ - and not validators[ex_tok.type_](cmp_tok): - return None - if ex_tok.code or ex_tok.type_ is _MsgTok.NICK_USER_HOST: - value = (cmp_tok if (isinstance(ex_tok.type_, str) - or ex_tok.type_ not in parsers) - else parsers[ex_tok.type_](cmp_tok)) - if ex_tok.type_ is _MsgTok.NICK_USER_HOST: - if not (ex_tok.code and - 'skipnuh' in [cmd.verb - for cmd in ex_tok.code.commands]): - nickuserhosts += [value] - if ex_tok.code: - parsed[ex_tok.code.title] = value - singled_tasks += [(cmd, ex_tok.code.title) - for cmd in ex_tok.code.commands - if cmd.verb != 'skipnuh'] - for code in self.bonus_tasks: - singled_tasks += [(cmd, code.title) for cmd in code.commands] - tasks: dict[_Command, list[str]] = {} - for cmd, title in singled_tasks: - if cmd not in tasks: - tasks[cmd] = [] - tasks[cmd] += [title] - return parsed | {'_verb': self.verb, - '_tasks': tasks, - '_nickuserhosts': nickuserhosts} + StrTuplable = str | tuple[str, ...] + RetDict = dict[str, StrTuplable | tuple[NickUserHost, ...]] + validators = self.VALIDATORS | {_MsgToken.CHANNEL: is_chan_name, + _MsgToken.NICKNAME: is_nick} + + def divide_msg(msg: IrcMessage) -> Optional[tuple[StrTuplable, ...]]: + from_msg: list[StrTuplable] = [] + idx_after_list = 0 + for idx, param in enumerate(self.params): + if param.type_ == _MsgToken.LIST: + remaining_exp_params = len(self.params) - idx + idx_after_list = len(msg.params) - remaining_exp_params + 1 + from_msg += [' '.join(msg.params[idx:idx_after_list])] + from_msg += list(msg.params[idx_after_list:]) + break + from_msg += msg.params[idx:idx + 1] # collect [] if none there + if len(from_msg) < len(msg.params) and not idx_after_list: + return None # not all msg.params collected + if len(self.params) != len(from_msg): + return None # msg.params are too few or too many + return tuple([msg.source] + from_msg) + + def harvest_msg(exp_fields: tuple[_TokenExpectation, ...], + msg_fields: tuple[StrTuplable, ...], + validators: _MsgTokenValidatorDict + ) -> Optional[RetDict]: + def parsed(type_: _MsgToken | str, to_parse: StrTuplable): + return self.PARSERS.get(type_, lambda keep: keep)(to_parse) + + d: RetDict = {} + nickuserhosts: list[NickUserHost] = [] + for exp_tok, msg_tok in [(exp_tok, msg_fields[idx]) + for idx, exp_tok + in enumerate(exp_fields)]: + if not validators.get(exp_tok.type_, lambda _: True)(msg_tok): + return None # validator found for this type, failed it + if isinstance(exp_tok.type_, str) and exp_tok.type_ != msg_tok: + return None # validator for "specific string" + if exp_tok.type_ is _MsgToken.NICK_USER_HOST\ + and not exp_tok.code.skip_nuh: + nickuserhosts += [parsed(exp_tok.type_, msg_tok)] + if exp_tok.code.tok_name: + d[exp_tok.code.tok_name] = parsed(exp_tok.type_, msg_tok) + return d | {'_nickuserhosts': tuple(nickuserhosts)} + + if (msg_fields := divide_msg(msg)): + exp_fields = tuple([self.source] + list(self.params)) + if (to_ret := harvest_msg(exp_fields, msg_fields, validators)): + tasks: dict[_Command, list[str]] = {} + for code in (tuple(exp_field.code for exp_field in exp_fields) + + self.bonus_tasks): + for cmd in code.commands: + tasks[cmd] = tasks.get(cmd, []) + [code.tok_name] + return to_ret | {'_verb': self.verb, '_tasks': tasks} + return None MSG_EXPECTATIONS: list[_MsgParseExpectation] = [ @@ -144,160 +159,160 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [ _MsgParseExpectation( '001', # RPL_WELCOME - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - _MsgTok.ANY), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.ANY), bonus_tasks=('do_autojoin:',)), _MsgParseExpectation( '002', # RPL_YOURHOST - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - _MsgTok.ANY)), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.ANY)), _MsgParseExpectation( '003', # RPL_CREATED - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - _MsgTok.ANY)), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.ANY)), _MsgParseExpectation( '004', # RPL_MYINFO - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - _MsgTok.ANY, - _MsgTok.ANY, - _MsgTok.ANY, - _MsgTok.ANY, - _MsgTok.ANY)), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.ANY, + _MsgToken.ANY, + _MsgToken.ANY, + _MsgToken.ANY, + _MsgToken.ANY)), _MsgParseExpectation( '250', # RPL_STATSDLINE / RPL_STATSCONN - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - _MsgTok.ANY)), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.ANY)), _MsgParseExpectation( '251', # RPL_LUSERCLIENT - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - _MsgTok.ANY)), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.ANY)), _MsgParseExpectation( '252', # RPL_LUSEROP - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - _MsgTok.ANY, - _MsgTok.ANY)), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.ANY, + _MsgToken.ANY)), _MsgParseExpectation( '253', # RPL_LUSERUNKNOWN - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - _MsgTok.ANY, - _MsgTok.ANY)), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.ANY, + _MsgToken.ANY)), _MsgParseExpectation( '254', # RPL_LUSERCHANNELS - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - _MsgTok.ANY, - _MsgTok.ANY)), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.ANY, + _MsgToken.ANY)), _MsgParseExpectation( '255', # RPL_LUSERME - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - _MsgTok.ANY)), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.ANY)), _MsgParseExpectation( '265', # RPL_LOCALUSERS - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - _MsgTok.ANY)), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.ANY)), _MsgParseExpectation( '265', # RPL_LOCALUSERS - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - _MsgTok.ANY, - _MsgTok.ANY, - _MsgTok.ANY)), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.ANY, + _MsgToken.ANY, + _MsgToken.ANY)), _MsgParseExpectation( '266', # RPL_GLOBALUSERS - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - _MsgTok.ANY)), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.ANY)), _MsgParseExpectation( '266', # RPL_GLOBALUSERS - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - _MsgTok.ANY, - _MsgTok.ANY, - _MsgTok.ANY)), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.ANY, + _MsgToken.ANY, + _MsgToken.ANY)), _MsgParseExpectation( '375', # RPL_MOTDSTART already implied by 1st 372 - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - _MsgTok.ANY)), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.ANY)), # various login stuff _MsgParseExpectation( '005', # RPL_ISUPPORT - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - (_MsgTok.LIST, 'do_db.set_isupport_from_rpl:isupport'), - _MsgTok.ANY)), # comment + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + (_MsgToken.LIST, 'do_db.set_isupport_from_rpl:isupport'), + _MsgToken.ANY)), # comment _MsgParseExpectation( '372', # RPL_MOTD - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - (_MsgTok.ANY, 'do_db.motd.append:line'))), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + (_MsgToken.ANY, 'do_db.motd.append:line'))), _MsgParseExpectation( '376', # RPL_ENDOFMOTD - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - _MsgTok.ANY), # comment + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.ANY), # comment bonus_tasks=('do_db.motd.complete:',)), _MsgParseExpectation( '396', # RPL_VISIBLEHOST - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - (_MsgTok.SERVER, 'setattr_db.users.me:host'), - _MsgTok.ANY)), # comment + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + (_MsgToken.SERVER, 'setattr_db.users.me:host'), + _MsgToken.ANY)), # comment # SASL _MsgParseExpectation( '900', # RPL_LOGGEDIN - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - (_MsgTok.NICK_USER_HOST, 'setattr_db.users.me:nickuserhost'), - (_MsgTok.ANY, 'setattr_db:sasl_account'), - _MsgTok.ANY)), # comment + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + (_MsgToken.NICK_USER_HOST, 'setattr_db.users.me:nickuserhost'), + (_MsgToken.ANY, 'setattr_db:sasl_account'), + _MsgToken.ANY)), # comment _MsgParseExpectation( '903', # RPL_SASLSUCCESS - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - (_MsgTok.ANY, 'setattr_db:sasl_auth_state')), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + (_MsgToken.ANY, 'setattr_db:sasl_auth_state')), bonus_tasks=('do_caps.end_negotiation:',)), _MsgParseExpectation( '904', # ERR_SASLFAIL - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - (_MsgTok.ANY, 'setattr_db:sasl_auth_state')), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + (_MsgToken.ANY, 'setattr_db:sasl_auth_state')), bonus_tasks=('do_caps.end_negotiation:',)), _MsgParseExpectation( 'AUTHENTICATE', - _MsgTok.NONE, + _MsgToken.NONE, ('+',), bonus_tasks=('do_send_authentication:',)), @@ -305,286 +320,286 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [ _MsgParseExpectation( 'CAP', - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), ('NEW', ':subverb'), - (_MsgTok.LIST, ':items'))), + (_MsgToken.LIST, ':items'))), _MsgParseExpectation( 'CAP', - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), ('DEL', ':subverb'), - (_MsgTok.LIST, ':items'))), + (_MsgToken.LIST, ':items'))), _MsgParseExpectation( 'CAP', - _MsgTok.SERVER, + _MsgToken.SERVER, ('*', ('ACK', ':subverb'), - (_MsgTok.LIST, ':items'))), + (_MsgToken.LIST, ':items'))), _MsgParseExpectation( 'CAP', - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), ('ACK', ':subverb'), - (_MsgTok.LIST, ':items'))), + (_MsgToken.LIST, ':items'))), _MsgParseExpectation( 'CAP', - _MsgTok.SERVER, + _MsgToken.SERVER, ('*', ('NAK', ':subverb'), - (_MsgTok.LIST, ':items'))), + (_MsgToken.LIST, ':items'))), _MsgParseExpectation( 'CAP', - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), ('NAK', ':subverb'), - (_MsgTok.LIST, ':items'))), + (_MsgToken.LIST, ':items'))), _MsgParseExpectation( 'CAP', - _MsgTok.SERVER, + _MsgToken.SERVER, ('*', ('LS', ':subverb'), ('*', ':tbc'), - (_MsgTok.LIST, ':items'))), + (_MsgToken.LIST, ':items'))), _MsgParseExpectation( 'CAP', - _MsgTok.SERVER, + _MsgToken.SERVER, ('*', ('LS', ':subverb'), - (_MsgTok.LIST, ':items'))), + (_MsgToken.LIST, ':items'))), _MsgParseExpectation( 'CAP', - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), ('LS', ':subverb'), - (_MsgTok.LIST, ':items'))), + (_MsgToken.LIST, ':items'))), _MsgParseExpectation( 'CAP', - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), ('LS', ':subverb'), ('*', ':tbc'), - (_MsgTok.LIST, ':items'))), + (_MsgToken.LIST, ':items'))), _MsgParseExpectation( 'CAP', - _MsgTok.SERVER, + _MsgToken.SERVER, ('*', ('LIST', ':subverb'), ('*', ':tbc'), - (_MsgTok.LIST, ':items'))), + (_MsgToken.LIST, ':items'))), _MsgParseExpectation( 'CAP', - _MsgTok.SERVER, + _MsgToken.SERVER, ('*', ('LIST', ':subverb'), - (_MsgTok.LIST, ':items'))), + (_MsgToken.LIST, ':items'))), _MsgParseExpectation( 'CAP', - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), ('LIST', ':subverb'), ('*', ':tbc'), - (_MsgTok.LIST, ':items'))), + (_MsgToken.LIST, ':items'))), _MsgParseExpectation( 'CAP', - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), ('LIST', ':subverb'), - (_MsgTok.LIST, ':items'))), + (_MsgToken.LIST, ':items'))), # nickname management _MsgParseExpectation( '432', # ERR_ERRONEOUSNICKNAME - _MsgTok.SERVER, + _MsgToken.SERVER, ('*', - _MsgTok.ANY, # bad one probably fails our NICKNAME tests - _MsgTok.ANY), # comment + _MsgToken.ANY, # bad one probably fails our NICKNAME tests + _MsgToken.ANY), # comment bonus_tasks=('do_close:',)), _MsgParseExpectation( '432', # ERR_ERRONEOUSNICKNAME - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - _MsgTok.ANY, # bad one probably fails our NICKNAME tests - _MsgTok.ANY)), # comment + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.ANY, # bad one probably fails our NICKNAME tests + _MsgToken.ANY)), # comment _MsgParseExpectation( '433', # ERR_NICKNAMEINUSE - _MsgTok.SERVER, + _MsgToken.SERVER, ('*', - (_MsgTok.NICKNAME, 'do_increment_for_nicknameinuse:rejected'), - _MsgTok.ANY)), # comment + (_MsgToken.NICKNAME, 'do_increment_for_nicknameinuse:rejected'), + _MsgToken.ANY)), # comment _MsgParseExpectation( '433', # ERR_NICKNAMEINUSE - _MsgTok.SERVER, - (_MsgTok.NICKNAME, # we rather go for incrementation - (_MsgTok.NICKNAME, 'do_increment_for_nicknameinuse:rejected'), - _MsgTok.ANY)), # comment + _MsgToken.SERVER, + (_MsgToken.NICKNAME, # we rather go for incrementation + (_MsgToken.NICKNAME, 'do_increment_for_nicknameinuse:rejected'), + _MsgToken.ANY)), # comment _MsgParseExpectation( 'NICK', - (_MsgTok.NICK_USER_HOST, ':named'), - ((_MsgTok.NICKNAME, ':nick'),)), + (_MsgToken.NICK_USER_HOST, ':named'), + ((_MsgToken.NICKNAME, ':nick'),)), # joining/leaving _MsgParseExpectation( '332', # RPL_TOPIC - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - (_MsgTok.CHANNEL, ':CHAN'), - (_MsgTok.ANY, 'setattr_db.channels.CHAN.topic:what'))), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + (_MsgToken.CHANNEL, ':CHAN'), + (_MsgToken.ANY, 'setattr_db.channels.CHAN.topic:what'))), _MsgParseExpectation( '333', # RPL_TOPICWHOTIME - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - (_MsgTok.CHANNEL, ':CHAN'), - (_MsgTok.NICK_USER_HOST, - 'skipnuh_,setattr_db.channels.CHAN.topic:who'), - (_MsgTok.ANY, ':timestamp')), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + (_MsgToken.CHANNEL, ':CHAN'), + (_MsgToken.NICK_USER_HOST, + f'{_TOK_SKIPNUH},setattr_db.channels.CHAN.topic:who'), + (_MsgToken.ANY, ':timestamp')), bonus_tasks=('doafter_db.channels.CHAN.topic.complete:',)), _MsgParseExpectation( '353', # RPL_NAMREPLY - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), '@', - (_MsgTok.CHANNEL, ':CHANNEL'), - (_MsgTok.LIST, 'do_db.channels.CHANNEL.add_from_namreply:names'))), + (_MsgToken.CHANNEL, ':CHANNEL'), + (_MsgToken.LIST, 'do_db.channels.CHANNEL.add_from_namreply:names'))), _MsgParseExpectation( '353', # RPL_NAMREPLY - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), '=', - (_MsgTok.CHANNEL, ':CHANNEL'), - (_MsgTok.LIST, 'do_db.channels.CHANNEL.add_from_namreply:names'))), + (_MsgToken.CHANNEL, ':CHANNEL'), + (_MsgToken.LIST, 'do_db.channels.CHANNEL.add_from_namreply:names'))), _MsgParseExpectation( '366', # RPL_ENDOFNAMES - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - (_MsgTok.CHANNEL, ':CHAN'), - _MsgTok.ANY), # comment + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + (_MsgToken.CHANNEL, ':CHAN'), + _MsgToken.ANY), # comment bonus_tasks=('doafter_db.channels.CHAN.user_ids.complete:',)), _MsgParseExpectation( 'JOIN', - (_MsgTok.NICK_USER_HOST, 'do_db.channels.CHANNEL.join_user:user'), - ((_MsgTok.CHANNEL, ':CHANNEL'),)), + (_MsgToken.NICK_USER_HOST, 'do_db.channels.CHANNEL.join_user:user'), + ((_MsgToken.CHANNEL, ':CHANNEL'),)), _MsgParseExpectation( 'PART', - (_MsgTok.NICK_USER_HOST, ':parter'), - ((_MsgTok.CHANNEL, ':channel'),)), + (_MsgToken.NICK_USER_HOST, ':parter'), + ((_MsgToken.CHANNEL, ':channel'),)), _MsgParseExpectation( 'PART', - (_MsgTok.NICK_USER_HOST, ':parter'), - ((_MsgTok.CHANNEL, ':channel'), - (_MsgTok.ANY, ':message'))), + (_MsgToken.NICK_USER_HOST, ':parter'), + ((_MsgToken.CHANNEL, ':channel'), + (_MsgToken.ANY, ':message'))), # messaging _MsgParseExpectation( '401', # ERR_NOSUCKNICK - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - (_MsgTok.NICKNAME, ':missing'), - _MsgTok.ANY)), # comment + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + (_MsgToken.NICKNAME, ':missing'), + _MsgToken.ANY)), # comment _MsgParseExpectation( 'NOTICE', - _MsgTok.SERVER, + _MsgToken.SERVER, ('*', - (_MsgTok.ANY, 'setattr_db.messaging..to.:notice'))), + (_MsgToken.ANY, 'setattr_db.messaging..to.:notice'))), _MsgParseExpectation( 'NOTICE', - _MsgTok.SERVER, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - (_MsgTok.ANY, 'setattr_db.messaging..to.:notice'))), + _MsgToken.SERVER, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + (_MsgToken.ANY, 'setattr_db.messaging..to.:notice'))), _MsgParseExpectation( 'NOTICE', - (_MsgTok.SERVER, ':SERVER'), - ((_MsgTok.CHANNEL, ':CHANNEL'), - (_MsgTok.ANY, 'setattr_db.messaging.SERVER.to.CHANNEL:notice'))), + (_MsgToken.SERVER, ':SERVER'), + ((_MsgToken.CHANNEL, ':CHANNEL'), + (_MsgToken.ANY, 'setattr_db.messaging.SERVER.to.CHANNEL:notice'))), _MsgParseExpectation( 'NOTICE', - (_MsgTok.NICK_USER_HOST, ':USER'), - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - (_MsgTok.ANY, 'setattr_db.messaging.USER.to.:notice'))), + (_MsgToken.NICK_USER_HOST, ':USER'), + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + (_MsgToken.ANY, 'setattr_db.messaging.USER.to.:notice'))), _MsgParseExpectation( 'NOTICE', - (_MsgTok.NICK_USER_HOST, ':USER'), - ((_MsgTok.CHANNEL, ':CHANNEL'), - (_MsgTok.ANY, 'setattr_db.messaging.USER.to.CHANNEL:notice'))), + (_MsgToken.NICK_USER_HOST, ':USER'), + ((_MsgToken.CHANNEL, ':CHANNEL'), + (_MsgToken.ANY, 'setattr_db.messaging.USER.to.CHANNEL:notice'))), _MsgParseExpectation( 'PRIVMSG', - (_MsgTok.NICK_USER_HOST, ':USER'), - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - (_MsgTok.ANY, 'setattr_db.messaging.USER.to.:privmsg'))), + (_MsgToken.NICK_USER_HOST, ':USER'), + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + (_MsgToken.ANY, 'setattr_db.messaging.USER.to.:privmsg'))), _MsgParseExpectation( 'PRIVMSG', - (_MsgTok.NICK_USER_HOST, ':USER'), - ((_MsgTok.CHANNEL, ':CHANNEL'), - (_MsgTok.ANY, 'setattr_db.messaging.USER.to.CHANNEL:privmsg'))), + (_MsgToken.NICK_USER_HOST, ':USER'), + ((_MsgToken.CHANNEL, ':CHANNEL'), + (_MsgToken.ANY, 'setattr_db.messaging.USER.to.CHANNEL:privmsg'))), # connection state _MsgParseExpectation( 'ERROR', - _MsgTok.NONE, - ((_MsgTok.ANY, 'setattr_db:connection_state'),), + _MsgToken.NONE, + ((_MsgToken.ANY, 'setattr_db:connection_state'),), bonus_tasks=('do_consider_retry:', 'doafter_close:',)), _MsgParseExpectation( 'PING', - _MsgTok.NONE, - ((_MsgTok.ANY, 'do_pong:reply'),)), + _MsgToken.NONE, + ((_MsgToken.ANY, 'do_pong:reply'),)), _MsgParseExpectation( 'PONG', - _MsgTok.SERVER, - (_MsgTok.SERVER, - (_MsgTok.ANY, 'do_check_pong:reply'),)), + _MsgToken.SERVER, + (_MsgToken.SERVER, + (_MsgToken.ANY, 'do_check_pong:reply'),)), # misc. _MsgParseExpectation( 'MODE', - (_MsgTok.NICK_USER_HOST, 'setattr_db.users.me:nickuserhost'), - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - (_MsgTok.ANY, 'setattr_db.users.me:modes'))), + (_MsgToken.NICK_USER_HOST, 'setattr_db.users.me:nickuserhost'), + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + (_MsgToken.ANY, 'setattr_db.users.me:modes'))), _MsgParseExpectation( 'MODE', - _MsgTok.NICKNAME, - ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), - (_MsgTok.ANY, 'setattr_db.users.me:modes'))), + _MsgToken.NICKNAME, + ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'), + (_MsgToken.ANY, 'setattr_db.users.me:modes'))), _MsgParseExpectation( 'MODE', - _MsgTok.SERVER, - ((_MsgTok.CHANNEL, ':channel'), - (_MsgTok.ANY, ':mode_on_nick'), - (_MsgTok.NICKNAME, ':nick'))), + _MsgToken.SERVER, + ((_MsgToken.CHANNEL, ':channel'), + (_MsgToken.ANY, ':mode_on_nick'), + (_MsgToken.NICKNAME, ':nick'))), _MsgParseExpectation( 'TOPIC', - (_MsgTok.NICK_USER_HOST, 'setattr_db.channels.CHAN.topic:who'), - ((_MsgTok.CHANNEL, ':CHAN'), - (_MsgTok.ANY, 'setattr_db.channels.CHAN.topic:what')), + (_MsgToken.NICK_USER_HOST, 'setattr_db.channels.CHAN.topic:who'), + ((_MsgToken.CHANNEL, ':CHAN'), + (_MsgToken.ANY, 'setattr_db.channels.CHAN.topic:what')), bonus_tasks=('doafter_db.channels.CHAN.topic.complete:',)), _MsgParseExpectation( 'QUIT', - (_MsgTok.NICK_USER_HOST, ':QUITTER'), - ((_MsgTok.ANY, 'do_QUITTER.quit:message'),)), + (_MsgToken.NICK_USER_HOST, ':QUITTER'), + ((_MsgToken.ANY, 'do_QUITTER.quit:message'),)), ]