home · contact · privacy
On MsgParsseExpectation bonus tasks allow multiple arguments.
authorChristian Heller <c.heller@plomlompom.de>
Mon, 24 Nov 2025 01:33:25 +0000 (02:33 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Mon, 24 Nov 2025 01:33:25 +0000 (02:33 +0100)
src/ircplom/client.py
src/ircplom/msg_parse_expectations.py

index 1c2444f57f42ca0094329dce00ca85121032f392..4e36d4ae4786acef0e39a1b374f44b4b3eda29c0 100644 (file)
@@ -485,6 +485,19 @@ class _ClientDb(Clearable, UpdatingAttrsMixin, SharedClientDbFields):
                 key, data = _tuple_key_val_from_eq_str(item)
                 self.isupport[key] = data
 
+    def set_nick_incl_wanted(self, user: _User, nick: str) -> None:
+        'Set new_nick to user, and .nick_wanted if ourselves.'
+        self.users[user.id_].nick = nick
+        if user.id_ == 'me':
+            self.nick_wanted = nick
+
+    def part_user_maybe_us(self, parter: _User, channel: str, msg='') -> None:
+        'Call parter.part, and if ourselves also remove channel.'
+        parter.part(channel, msg)
+        if parter is self.users['me']:
+            del self.channels[channel]
+            self.users.purge()
+
     def messaging(self, src: str | NickUserHost) -> ChatMessage:
         'Start input chain for chat message data.'
         return _ChatMessage(sender=src, db=self)
@@ -826,8 +839,8 @@ class Client(ABC, ClientQueueMixin):
                 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()
-                                    if t[0].verb == verb]:
+            for task, args in [t for t in ret['_tasks'].items()
+                               if t[0].verb == verb]:
                 path = list(task.path)
                 node: Any = (ret[path.pop(0)]
                              if task.path and path[0].isupper() else self)
@@ -836,11 +849,13 @@ class Client(ABC, ClientQueueMixin):
                     node = (node[key] if isinstance(node, Dict)
                             else (node(key) if callable(node)
                                   else getattr(node, key)))
-                for tok_name in tok_names:
+                for arg in args:
                     if task.verb == 'setattr':
-                        setattr(node, tok_name, ret[tok_name])
-                    elif tok_name:
-                        node(ret[tok_name])
+                        setattr(node, arg, ret[arg])
+                    elif ',' in arg:
+                        node(*(ret[split] for split in arg.split(',')))
+                    elif arg:
+                        node(ret[arg])
                     else:
                         node()
         if ret['_verb'] == '401':  # ERR_NOSUCHNICK
@@ -861,21 +876,6 @@ class Client(ABC, ClientQueueMixin):
                     self.send('AUTHENTICATE', _SASL_PLAIN)
                 else:
                     self.caps.end_negotiation()
-        elif ret['_verb'] == 'MODE' and 'mode_on_nick' in ret:
-            self.db.channels[ret['channel']].mode_on_nick(
-                    ret['nick'], ret['mode_on_nick'])
-        elif ret['_verb'] == 'NICK':
-            user_id = self.db.users.id_for_nickuserhost(ret['named'],
-                                                        updating=True)
-            assert user_id is not None
-            self.db.users[user_id].nick = ret['nick']
-            if user_id == 'me':
-                self.db.nick_wanted = ret['nick']
-        elif ret['_verb'] == 'PART':
-            ret['parter'].part(ret['channel'], ret.get('message', ''))
-            if ret['parter'] is self.db.users['me']:
-                del self.db.channels[ret['channel']]
-                self.db.users.purge()
 
 
 ClientsDb = dict[str, Client]
index 93ce1decd2759de15c2899dcc0332230d9d24ac1..de8c036d1ef2a71c584418baddfb2d24731ce0de 100644 (file)
@@ -36,17 +36,17 @@ class _Command(NamedTuple):
 
 
 class _Code(NamedTuple):
-    tok_name: str = ''
     commands: tuple[_Command, ...] = tuple()
+    arg: str = ''
     skip_nuh: bool = False
 
     def __bool__(self) -> bool:
-        return bool(self.tok_name) or bool(self.commands)
+        return bool(self.commands) or bool(self.arg)
 
     @classmethod
     def from_(cls, input_: str) -> Self:
-        'Split by ":" into commands (further split by ","), tok_name.'
-        commands_str, tok_name = input_.split(':', maxsplit=1)
+        'Split by ":" into commands (further split by ","), argument.'
+        commands_str, arg = input_.split(':', maxsplit=1)
         skip_nuh = False
         commands: list[_Command] = []
         for command_str in commands_str.split(','):
@@ -54,7 +54,7 @@ class _Code(NamedTuple):
                 skip_nuh = True
             elif command_str:
                 commands += [_Command.from_(command_str)]
-        return cls(tok_name, tuple(commands), skip_nuh)
+        return cls(tuple(commands), arg, skip_nuh)
 
 
 class _TokenExpectation(NamedTuple):
@@ -92,7 +92,7 @@ class _MsgParseExpectation:
         for code in (tuple(exp_field.code for exp_field in self._fields)
                      + tuple(_Code.from_(item) for item in bonus_tasks)):
             for cmd in code.commands:
-                tasks[cmd] = tasks.get(cmd, []) + [code.tok_name]
+                tasks[cmd] = tasks.get(cmd, []) + [code.arg]
         self._harvest_invariables = {'_verb': self.verb, '_tasks': tasks}
 
     @property
@@ -141,12 +141,12 @@ class _MsgParseExpectation:
                 if not validators.get(exp_tok.type_, lambda _: True)(msg_tok):
                     return None  # validator found for this type, but failed it
                 if isinstance(exp_tok.type_, str) and exp_tok.type_ != msg_tok:
-                    return None  # validator for specific string
+                    return None  # "specific string"-validator failed
                 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)
+                if exp_tok.code.arg:
+                    d[exp_tok.code.arg] = parsed(exp_tok.type_, msg_tok)
             return d | {'_nickuserhosts': tuple(nickuserhosts)}
 
         if (msg_fields := divide_msg(msg)):
@@ -445,8 +445,9 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
 
     _MsgParseExpectation(
         'NICK',
-        (_MsgToken.NICK_USER_HOST, ':named'),
-        ((_MsgToken.NICKNAME, ':nick'),)),
+        (_MsgToken.NICK_USER_HOST, ':user'),
+        ((_MsgToken.NICKNAME, ':nick'),),
+        bonus_tasks=('do_db.set_nick_incl_wanted:user,nick',)),
 
     # joining/leaving
 
@@ -498,12 +499,14 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
     _MsgParseExpectation(
         'PART',
         (_MsgToken.NICK_USER_HOST, ':parter'),
-        ((_MsgToken.CHANNEL, ':channel'),)),
+        ((_MsgToken.CHANNEL, ':channel'),),
+        bonus_tasks=('do_db.part_user_maybe_us:parter,channel',)),
     _MsgParseExpectation(
         'PART',
         (_MsgToken.NICK_USER_HOST, ':parter'),
         ((_MsgToken.CHANNEL, ':channel'),
-         (_MsgToken.ANY, ':message'))),
+         (_MsgToken.ANY, ':message')),
+        bonus_tasks=('do_db.part_user_maybe_us:parter,channel,message',)),
 
     # messaging
 
@@ -589,9 +592,10 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
     _MsgParseExpectation(
         'MODE',
         _MsgToken.SERVER,
-        ((_MsgToken.CHANNEL, ':channel'),
-         (_MsgToken.ANY, ':mode_on_nick'),
-         (_MsgToken.NICKNAME, ':nick'))),
+        ((_MsgToken.CHANNEL, ':CHANNEL'),
+         (_MsgToken.ANY, ':mode'),
+         (_MsgToken.NICKNAME, ':nick')),
+        bonus_tasks=('do_db.channels.CHANNEL.mode_on_nick:nick,mode',)),
 
     _MsgParseExpectation(
         'TOPIC',