home · contact · privacy
Treat Channel.modes_* as Completables, mark dictionary access in MsgParseExpectation... master
authorChristian Heller <c.heller@plomlompom.de>
Mon, 22 Dec 2025 04:24:04 +0000 (05:24 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Mon, 22 Dec 2025 04:24:04 +0000 (05:24 +0100)
src/ircplom/client.py
src/ircplom/client_tui.py
src/ircplom/db_primitives.py
src/ircplom/msg_parse_expectations.py
src/tests/channel_modes.test
src/tests/channels.test
src/tests/isupports.test
src/tests/lib/join-channel
src/tests/test.test

index 744bee0ff7e381d54b1ebdf38e7bd843a388c698..1bb742f416792d47b40faf701b7f40e4fb8c7e8e 100644 (file)
@@ -13,8 +13,8 @@ from uuid import UUID, uuid4
 from ircplom.db_primitives import (
         Clearable, Completable, CompletableStringsSet, DbLinked, DbLinking,
         Dict, DictItem, UpdatingAttrsMixin, UpdatingCompletable,
 from ircplom.db_primitives import (
         Clearable, Completable, CompletableStringsSet, DbLinked, DbLinking,
         Dict, DictItem, UpdatingAttrsMixin, UpdatingCompletable,
-        UpdatingCompletableStringsOrdered, UpdatingCompletableStringsSet,
-        UpdatingDict, UpdatingMixin)
+        UpdatingCompletableDict, UpdatingCompletableStringsOrdered,
+        UpdatingCompletableStringsSet, UpdatingDict, UpdatingMixin)
 from ircplom.events import (
         AffectiveEvent, CrashingException, ExceptionEvent, QueueMixin)
 from ircplom.irc_conn import (
 from ircplom.events import (
         AffectiveEvent, CrashingException, ExceptionEvent, QueueMixin)
 from ircplom.irc_conn import (
@@ -135,7 +135,7 @@ class Channel:
     exits: Dict[str]
     modes_listy: Dict[tuple[str, ...]]
     modes_valued: Dict[str]
     exits: Dict[str]
     modes_listy: Dict[tuple[str, ...]]
     modes_valued: Dict[str]
-    modes_toggled: str = ''
+    modes_toggled: Iterable[str]
 
 
 @dataclass
 
 
 @dataclass
@@ -190,8 +190,9 @@ class _Channel(UpdatingAttrsMixin, _DbLinked, Channel):
     user_ids: UpdatingCompletableStringsSet
     topic: _UpdatingCompletableTopic
     exits: UpdatingDict[str]
     user_ids: UpdatingCompletableStringsSet
     topic: _UpdatingCompletableTopic
     exits: UpdatingDict[str]
-    modes_listy: UpdatingDict[tuple[str, ...]]
-    modes_valued: UpdatingDict[str]
+    modes_listy: UpdatingCompletableDict[tuple[str, ...]]
+    modes_valued: UpdatingCompletableDict[str]
+    modes_toggled: UpdatingCompletableStringsSet
 
     def _id_from_nick(self, nick: str, create_if_none: bool) -> str:
         user_id = self._db.users.id_for_nickuserhost(NickUserHost(nick),
 
     def _id_from_nick(self, nick: str, create_if_none: bool) -> str:
         user_id = self._db.users.id_for_nickuserhost(NickUserHost(nick),
@@ -228,10 +229,12 @@ class _Channel(UpdatingAttrsMixin, _DbLinked, Channel):
         self.exits[user.id_] = msg
         self.user_ids.completable_remove(user.id_, on_complete=True)
         del self.exits[user.id_]
         self.exits[user.id_] = msg
         self.user_ids.completable_remove(user.id_, on_complete=True)
         del self.exits[user.id_]
+        self.modes_listy.completed = None
         for c in [c for c in self._db.get_membership_modes().keys()
                   if user.id_ in self.modes_listy.get(c, tuple())]:
             self.modes_listy[c] = tuple(uid for uid in self.modes_listy[c]
                                         if uid != user.id_)
         for c in [c for c in self._db.get_membership_modes().keys()
                   if user.id_ in self.modes_listy.get(c, tuple())]:
             self.modes_listy[c] = tuple(uid for uid in self.modes_listy[c]
                                         if uid != user.id_)
+        self.modes_listy.complete()
         self._db.users.purge()
 
     def set_modes(self, modeset: str, args_str='') -> None:
         self._db.users.purge()
 
     def set_modes(self, modeset: str, args_str='') -> None:
@@ -263,6 +266,9 @@ class _Channel(UpdatingAttrsMixin, _DbLinked, Channel):
         except AssertionError:
             raise ImplementationFail(  # pylint: disable=raise-missing-from
                     f'channel mode setting {modeset} on args: {args_str}')
         except AssertionError:
             raise ImplementationFail(  # pylint: disable=raise-missing-from
                     f'channel mode setting {modeset} on args: {args_str}')
+        modes_attrs = self.modes_listy, self.modes_valued, self.modes_toggled
+        for attr in modes_attrs:
+            attr.completed = None
         for do_add, char, arg in todos:
             if char in modes['A'] + prefix_modes:
                 char_modes = list(self.modes_listy.get(char, tuple()))
         for do_add, char, arg in todos:
             if char in modes['A'] + prefix_modes:
                 char_modes = list(self.modes_listy.get(char, tuple()))
@@ -276,10 +282,11 @@ class _Channel(UpdatingAttrsMixin, _DbLinked, Channel):
                 elif char in self.modes_valued.keys():
                     del self.modes_valued[char]
             else:  # char in chanmodes['D']
                 elif char in self.modes_valued.keys():
                     del self.modes_valued[char]
             else:  # char in chanmodes['D']
-                self.modes_toggled\
-                    = ''.join(sorted(set((self.modes_toggled + char) if do_add
-                                         else (c for c in self.modes_toggled
-                                               if c != char))))
+                getattr(self.modes_toggled,
+                        f'completable_{"add" if do_add else "remove"}'
+                        )(char, False)
+        for attr in modes_attrs:
+            attr.complete()
 
 
 class _SetNickuserhostMixin:
 
 
 class _SetNickuserhostMixin:
@@ -517,6 +524,11 @@ class _ClientDb(Clearable, UpdatingAttrsMixin, SharedClientDbFields, DbLinking
             if path not in self._updates_cache\
                     or val != self._updates_cache[path]:
                 self._updates_cache[path] = val
             if path not in self._updates_cache\
                     or val != self._updates_cache[path]:
                 self._updates_cache[path] = val
+                if val in (None, Dict()):
+                    for cache_path in [p for p in self._updates_cache
+                                       if len(p) > len(path)
+                                       and p[:len(path)] == path]:
+                        del self._updates_cache[cache_path]
                 return [(path, val)]
             return []
 
                 return [(path, val)]
             return []
 
@@ -533,23 +545,19 @@ class _ClientDb(Clearable, UpdatingAttrsMixin, SharedClientDbFields, DbLinking
                         else None) if isinstance(parent, Dict)
                        else getattr(parent, last_step))
 
                         else None) if isinstance(parent, Dict)
                        else getattr(parent, last_step))
 
-        # treat anything without UpdatingMixin as single endnode to return
+        # ignore Completables if not .completed
+        if isinstance(val_at_path, Completable):
+            if val_at_path.completed is None:
+                return []
+            val_at_path = val_at_path.completed
+
+        # treat any non-Dict without UpdatingMixin as single endnode to return
         # (NB: this would include None, as a deletion signal)
         # (NB: this would include None, as a deletion signal)
-        if not isinstance(val_at_path, UpdatingMixin):
+        if not isinstance(val_at_path, (Dict, UpdatingMixin)):
             return update_unless_cached(path, val_at_path)
 
             return update_unless_cached(path, val_at_path)
 
-        # for completable, only return its .completed
-        if isinstance(val_at_path, Completable):
-            if val_at_path.completed is not None:
-                return update_unless_cached(path, val_at_path.completed)
-            return []
-
         # for empty Dict, return one as clearing signal
         if isinstance(val_at_path, Dict) and not val_at_path.keys():
         # for empty Dict, return one as clearing signal
         if isinstance(val_at_path, Dict) and not val_at_path.keys():
-            for cache_path in [p for p in self._updates_cache
-                               if len(p) > len(path)
-                               and p[:len(path)] == path]:
-                del self._updates_cache[cache_path]
             return update_unless_cached(path, Dict())
 
         # if node at path has children, (only) return _their_ endnode updates
             return update_unless_cached(path, Dict())
 
         # if node at path has children, (only) return _their_ endnode updates
@@ -562,6 +570,12 @@ class _ClientDb(Clearable, UpdatingAttrsMixin, SharedClientDbFields, DbLinking
         for key in sub_items:
             p = path + (key,)
             updates += self.into_endnode_updates(p)
         for key in sub_items:
             p = path + (key,)
             updates += self.into_endnode_updates(p)
+
+        # signal absence of any _former_ sub_items siblings
+        for cache_path in [p for p in self._updates_cache
+                           if p[:-1] == path
+                           and p[-1] not in sub_items]:
+            updates += update_unless_cached(cache_path, None)
         return updates
 
     def clear(self) -> None:
         return updates
 
     def clear(self) -> None:
@@ -850,13 +864,16 @@ class Client(ABC, ClientQueueMixin):
             for task, args in [t for t in ret['_tasks'].items()
                                if t[0].verb == verb]:
                 path = list(task.path)
             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)
-                for step in path:
-                    key = ret[step] if step.isupper() else step
-                    node = (node[key] if isinstance(node, Dict)
-                            else (node(key) if callable(node)
-                                  else getattr(node, key)))
+                node: Any = (ret[path.pop(0)[1]]
+                             if task.path and path[0][1].isupper() else self)
+                for is_dict_key, step_name in path:
+                    if is_dict_key:
+                        if step_name.isupper():
+                            step_name = ret[step_name]
+                        node = node[step_name]
+                    else:
+                        node = (node(step_name) if callable(node)
+                                else getattr(node, step_name))
                 for arg in args:
                     if task.verb == 'setattr':
                         setattr(node, arg, ret[arg])
                 for arg in args:
                     if task.verb == 'setattr':
                         setattr(node, arg, ret[arg])
index 505108007efa2400f186ac6450a69325647902ab..5541a4cd4d3f9fa0b040091f00331309b537362c 100644 (file)
@@ -297,6 +297,7 @@ class _UpdatingChannel(_UpdatingNode, _DbLinked, Channel):
     exits: _UpdatingDict[str]
     modes_listy: _UpdatingDict[tuple[str, ...]]
     modes_valued: _UpdatingDict[str]
     exits: _UpdatingDict[str]
     modes_listy: _UpdatingDict[tuple[str, ...]]
     modes_valued: _UpdatingDict[str]
+    modes_toggled: set[str]
 
     def prefix_for(self, user_id: str) -> str:
         'Construct prefixes string for user of user_id.'
 
     def prefix_for(self, user_id: str) -> str:
         'Construct prefixes string for user of user_id.'
index 3cffcaba9aebc765167345c3f5d9fa1c4540bf50..c4cec3a0a04d4a940bbe57ba26b8b92c50920f49 100644 (file)
@@ -88,7 +88,7 @@ class Dict(Clearable, Generic[DictItem]):
         return tuple((val for val in self._dict.values()))
 
     def get(self, key: str, default: DictItem) -> DictItem:
         return tuple((val for val in self._dict.values()))
 
     def get(self, key: str, default: DictItem) -> DictItem:
-        'Wrapper around dict.get(key, default), excluding None returns.'
+        'Wrapper around dict.get(key, default), sans implicit None defaults.'
         return self._dict.get(key, default)
 
 
         return self._dict.get(key, default)
 
 
@@ -139,17 +139,11 @@ class CompletableStringsSet(_CompletableStringsCollection, Set):
 
     def _on_set(self, m_name: str, item: str, on_complete: bool, exists: bool
                 ) -> None:
 
     def _on_set(self, m_name: str, item: str, on_complete: bool, exists: bool
                 ) -> None:
-        method = getattr(self._collected, m_name)
+        assert (self.completed is not None) == on_complete
+        if (item in self._collected) == exists:
+            getattr(self._collected, m_name)(item)
         if on_complete:
         if on_complete:
-            assert self.completed is not None
-            assert (item in self.completed) == exists
-            assert (item in self._collected) == exists
-            method(item)
             self.complete()
             self.complete()
-        else:
-            assert self.completed is None
-            assert (item in self._collected) == exists
-            method(item)
 
     def completable_add(self, item: str, on_complete: bool) -> None:
         'Put item into collection.'
 
     def completable_add(self, item: str, on_complete: bool) -> None:
         'Put item into collection.'
@@ -187,6 +181,15 @@ class _CompletableStringsOrdered(Clearable, _CompletableStringsCollection):
         self.complete()
 
 
         self.complete()
 
 
+class _CompletableDict(Completable, Dict[DictItem]):
+    completed: Optional[Dict[DictItem]] = None
+
+    def complete(self) -> None:
+        self.completed: Dict[DictItem] = Dict()
+        for k, v in self._dict.items():
+            self.completed[k] = v
+
+
 class UpdatingMixin:
     'Ensures update trigger subclasses can call on any data changes.'
 
 class UpdatingMixin:
     'Ensures update trigger subclasses can call on any data changes.'
 
@@ -259,7 +262,11 @@ class UpdatingCompletableStringsOrdered(UpdatingCompletable,
     'Clearable, updating, completable tuple of strings.'
 
 
     'Clearable, updating, completable tuple of strings.'
 
 
-_DbType = TypeVar('_DbType')
+class UpdatingCompletableDict(UpdatingCompletable, _CompletableDict[DictItem]):
+    'Updating, completable Dict.'
+
+
+_DbType = TypeVar('_DbType', bound='DbLinking')
 _Same = TypeVar('_Same')
 
 
 _Same = TypeVar('_Same')
 
 
index 302044c60cf477643a8a80949825bc9a0dea726c..c5ba502f9934c01dc2d4bdcae1c44ea1a856877d 100644 (file)
@@ -25,14 +25,24 @@ _MsgTokenValidatorDict = dict[_MsgTokenWithStr, Callable[[Any], bool]]
 
 class _Command(NamedTuple):
     verb: str
 
 class _Command(NamedTuple):
     verb: str
-    path: tuple[str, ...]
+    path: tuple[tuple[bool, str], ...]
 
     @classmethod
     def from_(cls, input_: str) -> Self:
 
     @classmethod
     def from_(cls, input_: str) -> Self:
-        'Split by first "_" into verb, path (split into steps tuple by ".").'
+        'Split by first "_" into verb, path (split into steps tuple).'
         verb, path_str = input_.split('_', maxsplit=1)
         verb, path_str = input_.split('_', maxsplit=1)
-        return cls(verb, tuple(step for step in path_str.split('.')
-                               if path_str))
+        is_dict_key = False
+        cur_tok = ''
+        steps = []
+        for c in path_str + '.':
+            if c in '.[]':
+                if cur_tok:
+                    steps += [(is_dict_key, cur_tok)]
+                cur_tok = ''
+                is_dict_key = c == '['
+                continue
+            cur_tok += c
+        return cls(verb, tuple(steps))
 
 
 class _Code(NamedTuple):
 
 
 class _Code(NamedTuple):
@@ -162,26 +172,26 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
     _MsgParseExpectation(
         '001',  # RPL_WELCOME
         _MsgToken.SERVER,
     _MsgParseExpectation(
         '001',  # RPL_WELCOME
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          _MsgToken.ANY),
         bonus_tasks=('do_autojoin:',)),
 
     _MsgParseExpectation(
         '002',  # RPL_YOURHOST
         _MsgToken.SERVER,
          _MsgToken.ANY),
         bonus_tasks=('do_autojoin:',)),
 
     _MsgParseExpectation(
         '002',  # RPL_YOURHOST
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          _MsgToken.ANY)),
 
     _MsgParseExpectation(
         '003',  # RPL_CREATED
         _MsgToken.SERVER,
          _MsgToken.ANY)),
 
     _MsgParseExpectation(
         '003',  # RPL_CREATED
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          _MsgToken.ANY)),
 
     _MsgParseExpectation(
         '004',  # RPL_MYINFO
         _MsgToken.SERVER,
          _MsgToken.ANY)),
 
     _MsgParseExpectation(
         '004',  # RPL_MYINFO
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          _MsgToken.ANY,
          _MsgToken.ANY,
          _MsgToken.ANY,
          _MsgToken.ANY,
          _MsgToken.ANY,
          _MsgToken.ANY,
@@ -191,51 +201,51 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
     _MsgParseExpectation(
         '250',  # RPL_STATSDLINE / RPL_STATSCONN
         _MsgToken.SERVER,
     _MsgParseExpectation(
         '250',  # RPL_STATSDLINE / RPL_STATSCONN
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          _MsgToken.ANY)),
 
     _MsgParseExpectation(
         '251',  # RPL_LUSERCLIENT
         _MsgToken.SERVER,
          _MsgToken.ANY)),
 
     _MsgParseExpectation(
         '251',  # RPL_LUSERCLIENT
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          _MsgToken.ANY)),
 
     _MsgParseExpectation(
         '252',  # RPL_LUSEROP
         _MsgToken.SERVER,
          _MsgToken.ANY)),
 
     _MsgParseExpectation(
         '252',  # RPL_LUSEROP
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          _MsgToken.ANY,
          _MsgToken.ANY)),
 
     _MsgParseExpectation(
         '253',  # RPL_LUSERUNKNOWN
         _MsgToken.SERVER,
          _MsgToken.ANY,
          _MsgToken.ANY)),
 
     _MsgParseExpectation(
         '253',  # RPL_LUSERUNKNOWN
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          _MsgToken.ANY,
          _MsgToken.ANY)),
 
     _MsgParseExpectation(
         '254',  # RPL_LUSERCHANNELS
         _MsgToken.SERVER,
          _MsgToken.ANY,
          _MsgToken.ANY)),
 
     _MsgParseExpectation(
         '254',  # RPL_LUSERCHANNELS
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          _MsgToken.ANY,
          _MsgToken.ANY)),
 
     _MsgParseExpectation(
         '255',  # RPL_LUSERME
         _MsgToken.SERVER,
          _MsgToken.ANY,
          _MsgToken.ANY)),
 
     _MsgParseExpectation(
         '255',  # RPL_LUSERME
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          _MsgToken.ANY)),
 
     _MsgParseExpectation(
         '265',  # RPL_LOCALUSERS
         _MsgToken.SERVER,
          _MsgToken.ANY)),
 
     _MsgParseExpectation(
         '265',  # RPL_LOCALUSERS
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          _MsgToken.ANY)),
     _MsgParseExpectation(
         '265',  # RPL_LOCALUSERS
         _MsgToken.SERVER,
          _MsgToken.ANY)),
     _MsgParseExpectation(
         '265',  # RPL_LOCALUSERS
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          _MsgToken.ANY,
          _MsgToken.ANY,
          _MsgToken.ANY)),
          _MsgToken.ANY,
          _MsgToken.ANY,
          _MsgToken.ANY)),
@@ -243,12 +253,12 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
     _MsgParseExpectation(
         '266',  # RPL_GLOBALUSERS
         _MsgToken.SERVER,
     _MsgParseExpectation(
         '266',  # RPL_GLOBALUSERS
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          _MsgToken.ANY)),
     _MsgParseExpectation(
         '266',  # RPL_GLOBALUSERS
         _MsgToken.SERVER,
          _MsgToken.ANY)),
     _MsgParseExpectation(
         '266',  # RPL_GLOBALUSERS
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          _MsgToken.ANY,
          _MsgToken.ANY,
          _MsgToken.ANY)),
          _MsgToken.ANY,
          _MsgToken.ANY,
          _MsgToken.ANY)),
@@ -256,7 +266,7 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
     _MsgParseExpectation(
         '375',  # RPL_MOTDSTART already implied by 1st 372
         _MsgToken.SERVER,
     _MsgParseExpectation(
         '375',  # RPL_MOTDSTART already implied by 1st 372
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          _MsgToken.ANY)),
 
     # various login stuff
          _MsgToken.ANY)),
 
     # various login stuff
@@ -264,28 +274,28 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
     _MsgParseExpectation(
         '005',  # RPL_ISUPPORT
         _MsgToken.SERVER,
     _MsgParseExpectation(
         '005',  # RPL_ISUPPORT
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          (_MsgToken.LIST, 'do_db.set_isupport_from_rpl:isupport'),
          _MsgToken.ANY)),  # comment
 
     _MsgParseExpectation(
         '372',  # RPL_MOTD
         _MsgToken.SERVER,
          (_MsgToken.LIST, 'do_db.set_isupport_from_rpl:isupport'),
          _MsgToken.ANY)),  # comment
 
     _MsgParseExpectation(
         '372',  # RPL_MOTD
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          (_MsgToken.ANY, 'do_db.motd.append:line'))),
 
     _MsgParseExpectation(
         '376',  # RPL_ENDOFMOTD
         _MsgToken.SERVER,
          (_MsgToken.ANY, 'do_db.motd.append:line'))),
 
     _MsgParseExpectation(
         '376',  # RPL_ENDOFMOTD
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          _MsgToken.ANY),  # comment
         bonus_tasks=('do_db.motd.complete:',)),
 
     _MsgParseExpectation(
         '396',  # RPL_VISIBLEHOST
         _MsgToken.SERVER,
          _MsgToken.ANY),  # comment
         bonus_tasks=('do_db.motd.complete:',)),
 
     _MsgParseExpectation(
         '396',  # RPL_VISIBLEHOST
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
-         (_MsgToken.SERVER, 'setattr_db.users.me:host'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
+         (_MsgToken.SERVER, 'setattr_db.users[me]:host'),
          _MsgToken.ANY)),  # comment
 
     # SASL
          _MsgToken.ANY)),  # comment
 
     # SASL
@@ -293,22 +303,22 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
     _MsgParseExpectation(
         '900',  # RPL_LOGGEDIN
         _MsgToken.SERVER,
     _MsgParseExpectation(
         '900',  # RPL_LOGGEDIN
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
-         (_MsgToken.NICK_USER_HOST, 'setattr_db.users.me:nickuserhost'),
+        ((_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
         _MsgToken.SERVER,
          (_MsgToken.ANY, 'setattr_db:sasl_account'),
          _MsgToken.ANY)),  # comment
 
     _MsgParseExpectation(
         '903',  # RPL_SASLSUCCESS
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          (_MsgToken.ANY, 'setattr_db:sasl_auth_state')),
         bonus_tasks=('do_caps.end_negotiation:',)),
 
     _MsgParseExpectation(
         '904',  # ERR_SASLFAIL
         _MsgToken.SERVER,
          (_MsgToken.ANY, 'setattr_db:sasl_auth_state')),
         bonus_tasks=('do_caps.end_negotiation:',)),
 
     _MsgParseExpectation(
         '904',  # ERR_SASLFAIL
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          (_MsgToken.ANY, 'setattr_db:sasl_auth_state')),
         bonus_tasks=('do_caps.end_negotiation:',)),
 
          (_MsgToken.ANY, 'setattr_db:sasl_auth_state')),
         bonus_tasks=('do_caps.end_negotiation:',)),
 
@@ -323,14 +333,14 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
     _MsgParseExpectation(
         'CAP',
         _MsgToken.SERVER,
     _MsgParseExpectation(
         'CAP',
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          ('NEW', ':subverb'),
          (_MsgToken.LIST, ':items'))),
 
     _MsgParseExpectation(
         'CAP',
         _MsgToken.SERVER,
          ('NEW', ':subverb'),
          (_MsgToken.LIST, ':items'))),
 
     _MsgParseExpectation(
         'CAP',
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          ('DEL', ':subverb'),
          (_MsgToken.LIST, ':items'))),
 
          ('DEL', ':subverb'),
          (_MsgToken.LIST, ':items'))),
 
@@ -343,7 +353,7 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
     _MsgParseExpectation(
         'CAP',
         _MsgToken.SERVER,
     _MsgParseExpectation(
         'CAP',
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          ('ACK', ':subverb'),
          (_MsgToken.LIST, ':items'))),
 
          ('ACK', ':subverb'),
          (_MsgToken.LIST, ':items'))),
 
@@ -356,7 +366,7 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
     _MsgParseExpectation(
         'CAP',
         _MsgToken.SERVER,
     _MsgParseExpectation(
         'CAP',
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          ('NAK', ':subverb'),
          (_MsgToken.LIST, ':items'))),
 
          ('NAK', ':subverb'),
          (_MsgToken.LIST, ':items'))),
 
@@ -376,13 +386,13 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
     _MsgParseExpectation(
         'CAP',
         _MsgToken.SERVER,
     _MsgParseExpectation(
         'CAP',
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          ('LS', ':subverb'),
          (_MsgToken.LIST, ':items'))),
     _MsgParseExpectation(
         'CAP',
         _MsgToken.SERVER,
          ('LS', ':subverb'),
          (_MsgToken.LIST, ':items'))),
     _MsgParseExpectation(
         'CAP',
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          ('LS', ':subverb'),
          ('*', ':tbc'),
          (_MsgToken.LIST, ':items'))),
          ('LS', ':subverb'),
          ('*', ':tbc'),
          (_MsgToken.LIST, ':items'))),
@@ -403,14 +413,14 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
     _MsgParseExpectation(
         'CAP',
         _MsgToken.SERVER,
     _MsgParseExpectation(
         'CAP',
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          ('LIST', ':subverb'),
          ('*', ':tbc'),
          (_MsgToken.LIST, ':items'))),
     _MsgParseExpectation(
         'CAP',
         _MsgToken.SERVER,
          ('LIST', ':subverb'),
          ('*', ':tbc'),
          (_MsgToken.LIST, ':items'))),
     _MsgParseExpectation(
         'CAP',
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          ('LIST', ':subverb'),
          (_MsgToken.LIST, ':items'))),
 
          ('LIST', ':subverb'),
          (_MsgToken.LIST, ':items'))),
 
@@ -426,7 +436,7 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
     _MsgParseExpectation(
         '432',  # ERR_ERRONEOUSNICKNAME
         _MsgToken.SERVER,
     _MsgParseExpectation(
         '432',  # ERR_ERRONEOUSNICKNAME
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          _MsgToken.ANY,  # bad one probably fails our NICKNAME tests
          _MsgToken.ANY)),  # comment
 
          _MsgToken.ANY,  # bad one probably fails our NICKNAME tests
          _MsgToken.ANY)),  # comment
 
@@ -454,46 +464,47 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
     _MsgParseExpectation(
         '332',  # RPL_TOPIC
         _MsgToken.SERVER,
     _MsgParseExpectation(
         '332',  # RPL_TOPIC
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          (_MsgToken.CHANNEL, ':CHAN'),
          (_MsgToken.CHANNEL, ':CHAN'),
-         (_MsgToken.ANY, 'setattr_db.channels.CHAN.topic:what'))),
+         (_MsgToken.ANY, 'setattr_db.channels[CHAN].topic:what'))),
 
     _MsgParseExpectation(
         '333',  # RPL_TOPICWHOTIME
         _MsgToken.SERVER,
 
     _MsgParseExpectation(
         '333',  # RPL_TOPICWHOTIME
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          (_MsgToken.CHANNEL, ':CHAN'),
          (_MsgToken.NICK_USER_HOST,
          (_MsgToken.CHANNEL, ':CHAN'),
          (_MsgToken.NICK_USER_HOST,
-          f'{_TOK_SKIPNUH},setattr_db.channels.CHAN.topic:who'),
+          f'{_TOK_SKIPNUH},setattr_db.channels[CHAN].topic:who'),
          (_MsgToken.ANY, ':timestamp')),
          (_MsgToken.ANY, ':timestamp')),
-        bonus_tasks=('doafter_db.channels.CHAN.topic.complete:',)),
+        bonus_tasks=('doafter_db.channels[CHAN].topic.complete:',)),
 
     _MsgParseExpectation(
         '353',  # RPL_NAMREPLY
         _MsgToken.SERVER,
 
     _MsgParseExpectation(
         '353',  # RPL_NAMREPLY
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          '@',
          (_MsgToken.CHANNEL, ':CHANNEL'),
          '@',
          (_MsgToken.CHANNEL, ':CHANNEL'),
-         (_MsgToken.LIST, 'do_db.channels.CHANNEL.add_from_namreply:names'))),
+         (_MsgToken.LIST, 'do_db.channels[CHANNEL].add_from_namreply:names'))),
     _MsgParseExpectation(
         '353',  # RPL_NAMREPLY
         _MsgToken.SERVER,
     _MsgParseExpectation(
         '353',  # RPL_NAMREPLY
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          '=',
          (_MsgToken.CHANNEL, ':CHANNEL'),
          '=',
          (_MsgToken.CHANNEL, ':CHANNEL'),
-         (_MsgToken.LIST, 'do_db.channels.CHANNEL.add_from_namreply:names'))),
+         (_MsgToken.LIST, 'do_db.channels[CHANNEL].add_from_namreply:names'))),
 
     _MsgParseExpectation(
         '366',  # RPL_ENDOFNAMES
         _MsgToken.SERVER,
 
     _MsgParseExpectation(
         '366',  # RPL_ENDOFNAMES
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          (_MsgToken.CHANNEL, ':CHAN'),
          _MsgToken.ANY),  # comment
          (_MsgToken.CHANNEL, ':CHAN'),
          _MsgToken.ANY),  # comment
-        bonus_tasks=('doafter_db.channels.CHAN.user_ids.complete:',)),
+        bonus_tasks=('doafter_db.channels[CHAN].modes_listy.complete:',
+                     'doafter_db.channels[CHAN].user_ids.complete:')),
 
     _MsgParseExpectation(
         'JOIN',
 
     _MsgParseExpectation(
         'JOIN',
-        (_MsgToken.NICK_USER_HOST, 'do_db.channels.CHANNEL.join_user:user'),
+        (_MsgToken.NICK_USER_HOST, 'do_db.channels[CHANNEL].join_user:user'),
         ((_MsgToken.CHANNEL, ':CHANNEL'),)),
 
     _MsgParseExpectation(
         ((_MsgToken.CHANNEL, ':CHANNEL'),)),
 
     _MsgParseExpectation(
@@ -513,7 +524,7 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
     _MsgParseExpectation(
         '401',  # ERR_NOSUCKNICK
         _MsgToken.SERVER,
     _MsgParseExpectation(
         '401',  # ERR_NOSUCKNICK
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          (_MsgToken.NICKNAME, ':missing'),
          _MsgToken.ANY)),  # comment
 
          (_MsgToken.NICKNAME, ':missing'),
          _MsgToken.ANY)),  # comment
 
@@ -526,7 +537,7 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
     _MsgParseExpectation(
         'NOTICE',
         _MsgToken.SERVER,
     _MsgParseExpectation(
         'NOTICE',
         _MsgToken.SERVER,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
          (_MsgToken.ANY, ':content')),
         bonus_tasks=('do_db.notice:content',)),
 
          (_MsgToken.ANY, ':content')),
         bonus_tasks=('do_db.notice:content',)),
 
@@ -540,7 +551,7 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
     _MsgParseExpectation(
         'NOTICE',
         (_MsgToken.NICK_USER_HOST, ':sender'),
     _MsgParseExpectation(
         'NOTICE',
         (_MsgToken.NICK_USER_HOST, ':sender'),
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:target'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:target'),
          (_MsgToken.ANY, ':content')),
         bonus_tasks=('do_db.notice:content,sender',)),
 
          (_MsgToken.ANY, ':content')),
         bonus_tasks=('do_db.notice:content,sender',)),
 
@@ -554,7 +565,7 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
     _MsgParseExpectation(
         'PRIVMSG',
         (_MsgToken.NICK_USER_HOST, ':sender'),
     _MsgParseExpectation(
         'PRIVMSG',
         (_MsgToken.NICK_USER_HOST, ':sender'),
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:target'),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:target'),
          (_MsgToken.ANY, ':content')),
         bonus_tasks=('do_db.privmsg:content,sender',)),
     _MsgParseExpectation(
          (_MsgToken.ANY, ':content')),
         bonus_tasks=('do_db.privmsg:content,sender',)),
     _MsgParseExpectation(
@@ -587,34 +598,34 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
 
     _MsgParseExpectation(
         'MODE',
 
     _MsgParseExpectation(
         'MODE',
-        (_MsgToken.NICK_USER_HOST, 'setattr_db.users.me:nickuserhost'),
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
-         (_MsgToken.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',
         _MsgToken.NICKNAME,
     _MsgParseExpectation(
         'MODE',
         _MsgToken.NICKNAME,
-        ((_MsgToken.NICKNAME, 'setattr_db.users.me:nick'),
-         (_MsgToken.ANY, 'setattr_db.users.me:modes'))),
+        ((_MsgToken.NICKNAME, 'setattr_db.users[me]:nick'),
+         (_MsgToken.ANY, 'setattr_db.users[me]:modes'))),
 
     _MsgParseExpectation(
         'MODE',
         _MsgToken.SERVER,
         ((_MsgToken.CHANNEL, ':CHANNEL'),
 
     _MsgParseExpectation(
         'MODE',
         _MsgToken.SERVER,
         ((_MsgToken.CHANNEL, ':CHANNEL'),
-         (_MsgToken.ANY, 'do_db.channels.CHANNEL.set_modes:modeset'))),
+         (_MsgToken.ANY, 'do_db.channels[CHANNEL].set_modes:modeset'))),
     _MsgParseExpectation(
         'MODE',
         _MsgToken.SERVER,
         ((_MsgToken.CHANNEL, ':CHANNEL'),
          (_MsgToken.ANY, ':modeset'),
          (_MsgToken.ANY, ':args')),
     _MsgParseExpectation(
         'MODE',
         _MsgToken.SERVER,
         ((_MsgToken.CHANNEL, ':CHANNEL'),
          (_MsgToken.ANY, ':modeset'),
          (_MsgToken.ANY, ':args')),
-        bonus_tasks=('do_db.channels.CHANNEL.set_modes:modeset,args',)),
+        bonus_tasks=('do_db.channels[CHANNEL].set_modes:modeset,args',)),
 
     _MsgParseExpectation(
         'TOPIC',
 
     _MsgParseExpectation(
         'TOPIC',
-        (_MsgToken.NICK_USER_HOST, 'setattr_db.channels.CHAN.topic:who'),
+        (_MsgToken.NICK_USER_HOST, 'setattr_db.channels[CHAN].topic:who'),
         ((_MsgToken.CHANNEL, ':CHAN'),
         ((_MsgToken.CHANNEL, ':CHAN'),
-         (_MsgToken.ANY, 'setattr_db.channels.CHAN.topic:what')),
-        bonus_tasks=('doafter_db.channels.CHAN.topic.complete:',)),
+         (_MsgToken.ANY, 'setattr_db.channels[CHAN].topic:what')),
+        bonus_tasks=('doafter_db.channels[CHAN].topic.complete:',)),
 
     _MsgParseExpectation(
         'QUIT',
 
     _MsgParseExpectation(
         'QUIT',
index 114fe2a02c90da41b0abab9d48150f0f7528d5b5..c29e9577883843defff60fea5bd3e0316ab82c67 100644 (file)
@@ -11,20 +11,6 @@ insert ./lib/user-set-to
 × servermsg-mode
 insert servermsglogged [% (MSG)=:foo.bar.baz%MODE%#ch_win3%(ARGS)]
 
 × servermsg-mode
 insert servermsglogged [% (MSG)=:foo.bar.baz%MODE%#ch_win3%(ARGS)]
 
-× prefix-log
-log 1 $ channels:#ch_win3:modes_listy:(PREFIX) (NEW_IDS)
-log 3 $ (NICK) (VERB) (PREFIX)
-
-× set-prefix
-insert servermsg-mode [% (ARGS)=(MODESET)%(NICK)]
-insert prefix-log
-
-× prefix+
-insert set-prefix [(MODESET)=+(CHAR) (VERB)=gains]
-
-× prefix-
-insert set-prefix [(MODESET)=-(CHAR) (VERB)=loses]
-
 × msg-prefixed-long
 insert servermsglogged [% (MSG)=:(NICK)!~(NICK)(NICK)@(NICK).(NICK)%PRIVMSG%#ch_win3%:(NICK)%(NICK)%(NICK)]
 insert user-set-to range=1: [(USER_USER)=~(NICK)(NICK) (USER_HOST)=(NICK).(NICK)]
 × msg-prefixed-long
 insert servermsglogged [% (MSG)=:(NICK)!~(NICK)(NICK)@(NICK).(NICK)%PRIVMSG%#ch_win3%:(NICK)%(NICK)%(NICK)]
 insert user-set-to range=1: [(USER_USER)=~(NICK)(NICK) (USER_HOST)=(NICK).(NICK)]
@@ -42,14 +28,14 @@ insert isupport-set [(KEY)=CHANMODES (VALUE)=lmn,k,xy,abcABC]
 # check /join into channel with many other users, with multi-line 353, and membership prefixes
 insert join-channel-0-cmd-to-list-residents [% (WIN_ID)=3 (RESIDENT_NAMES)=foo%@baz%+oof]
 insert user-set-to range=:1 [(USER_ID)=1 (USER_NICK)=baz]
 # check /join into channel with many other users, with multi-line 353, and membership prefixes
 insert join-channel-0-cmd-to-list-residents [% (WIN_ID)=3 (RESIDENT_NAMES)=foo%@baz%+oof]
 insert user-set-to range=:1 [(USER_ID)=1 (USER_NICK)=baz]
-log 1 $ channels:#ch_win3:modes_listy:o set to: [1]
 insert user-set-to range=:1 [(USER_ID)=2 (USER_NICK)=oof]
 insert user-set-to range=:1 [(USER_ID)=2 (USER_NICK)=oof]
-log 1 $ channels:#ch_win3:modes_listy:v set to: [2]
 insert servermsglogged [% (MSG)=:foo.bar.baz%353%foo%=%#ch_win3%:+rab%zab]
 insert user-set-to range=:1 [(USER_ID)=3 (USER_NICK)=rab]
 insert servermsglogged [% (MSG)=:foo.bar.baz%353%foo%=%#ch_win3%:+rab%zab]
 insert user-set-to range=:1 [(USER_ID)=3 (USER_NICK)=rab]
-log 1 $ channels:#ch_win3:modes_listy:v set to: [2], [3]
 insert user-set-to range=:1 [(USER_ID)=4 (USER_NICK)=zab]
 insert user-set-to range=:1 [(USER_ID)=4 (USER_NICK)=zab]
-insert join-channel-1-end-of-names [% (WIN_ID)=3 (RESIDENT_IDS)=[1],%[2],%[3],%[4],%[me] (RESIDENT_NICKS)=@baz,%+oof,%+rab,%zab,%foo]
+insert join-channel-1-end-of-names range=:-3 [(WIN_ID)=3]
+log 1 $ channels:#ch_win3:modes_listy:o set to: [1]
+log 1 $ channels:#ch_win3:modes_listy:v set to: [2], [3]
+insert join-channel-1-end-of-names range=-2: [% (WIN_ID)=3 (RESIDENT_IDS)=[1],%[2],%[3],%[4],%[me] (RESIDENT_NICKS)=@baz,%+oof,%+rab,%zab,%foo]
 
 # check (presence/absence of) membership prefixes mirrored in message display
 insert privmsg-win [% (WIN_ID)=3 (TXT)=foo%foo%foo]
 
 # check (presence/absence of) membership prefixes mirrored in message display
 insert privmsg-win [% (WIN_ID)=3 (TXT)=foo%foo%foo]
@@ -59,19 +45,23 @@ insert msg-prefixed-long [(USER_ID)=3 (NICK)=rab (PREFIX)=+ PRIVMSG=NOTICE [+rab
 insert msg-prefixed-long [(USER_ID)=4 (NICK)=zab (PREFIX)=]
 
 # check server giving and taking membership prefixes
 insert msg-prefixed-long [(USER_ID)=4 (NICK)=zab (PREFIX)=]
 
 # check server giving and taking membership prefixes
-insert prefix- [(CHAR)=o (PREFIX)=o (NICK)=baz (NEW_IDS)=emptied]
-insert prefix- [% (CHAR)=v (PREFIX)=v (NICK)=rab (NEW_IDS)=set%to:%[2]]
-insert servermsg-mode [% (ARGS)=-v+oo%:oof%zab%baz]
-insert prefix-log [% (PREFIX)=v (VERB)=loses (NICK)=oof (NEW_IDS)=emptied]
-insert prefix-log [% (PREFIX)=o (VERB)=gains (NICK)=zab (NEW_IDS)=set%to:%[4]]
-insert prefix-log [% (PREFIX)=o (VERB)=gains (NICK)=baz (NEW_IDS)=set%to:%[1],%[4]]
-insert prefix+ [% (CHAR)=v (PREFIX)=v (NICK)=oof (NEW_IDS)=set%to:%[2]]
-insert prefix+ [% (CHAR)=o (PREFIX)=o (NICK)=foo (NEW_IDS)=set%to:%[1],%[4],%[me]]
+insert servermsg-mode [% (ARGS)=-o%baz]
+log 1 $ channels:#ch_win3:modes_listy:o emptied
+log 3 $ baz loses o
+insert servermsg-mode [% (ARGS)=-v%rab]
+log 1 $ channels:#ch_win3:modes_listy:v set to: [2]
+log 3 $ rab loses v
+insert servermsg-mode [% (ARGS)=-v+oov%:oof%zab%baz%foo]
+log 1 $ channels:#ch_win3:modes_listy:o set to: [1], [4]
+log 3 $ baz gains o
+log 3 $ zab gains o
+log 1 $ channels:#ch_win3:modes_listy:v set to: [me]
+log 3 $ foo gains v
+log 3 $ oof loses v
 
 # re-check membership prefix mirorring in message display after re-distribution
 
 # re-check membership prefix mirorring in message display after re-distribution
-insert privmsg-win [% (WIN_ID)=3 (TXT)=foo%foo%foo [foo]=[@foo]]
+insert privmsg-win [% (WIN_ID)=3 (TXT)=foo%foo%foo [foo]=[+foo]]
 insert msg-prefixed-short [(USER_ID)=1 (NICK)=baz (PREFIX)=@]
 insert msg-prefixed-short [(USER_ID)=1 (NICK)=baz (PREFIX)=@]
-insert msg-prefixed-short [(USER_ID)=2 (NICK)=oof (PREFIX)=+]
 insert msg-prefixed-short [(USER_ID)=3 (NICK)=rab (PREFIX)= PRIVMSG=NOTICE [rab]=(rab)]
 insert msg-prefixed-short [(USER_ID)=4 (NICK)=zab (PREFIX)=@]
 
 insert msg-prefixed-short [(USER_ID)=3 (NICK)=rab (PREFIX)= PRIVMSG=NOTICE [rab]=(rab)]
 insert msg-prefixed-short [(USER_ID)=4 (NICK)=zab (PREFIX)=@]
 
@@ -79,7 +69,7 @@ insert msg-prefixed-short [(USER_ID)=4 (NICK)=zab (PREFIX)=@]
 insert join-channel-0-cmd-to-list-residents [% (WIN_ID)=4 (RESIDENT_NAMES)=foo%baz]
 insert join-channel-1-end-of-names [% (WIN_ID)=4 (RESIDENT_IDS)=[1],%[me] (RESIDENT_NICKS)=baz,%foo]
 insert part-no-msg-other [% (USER_ID)=1 (NICK)=baz (REMAINING_IDS)=[2],%[3],%[4],%[me]]
 insert join-channel-0-cmd-to-list-residents [% (WIN_ID)=4 (RESIDENT_NAMES)=foo%baz]
 insert join-channel-1-end-of-names [% (WIN_ID)=4 (RESIDENT_IDS)=[1],%[me] (RESIDENT_NICKS)=baz,%foo]
 insert part-no-msg-other [% (USER_ID)=1 (NICK)=baz (REMAINING_IDS)=[2],%[3],%[4],%[me]]
-log 1 $ channels:#ch_win3:modes_listy:o set to: [4], [me]
+log 1 $ channels:#ch_win3:modes_listy:o set to: [4]
 insert servermsglogged [% (MSG)=:baz!~bazbaz@baz.baz%JOIN%:#ch_win3]
 log 1 $ channels:#ch_win3:user_ids set to: [1], [2], [3], [4], [me]
 log 3 $ baz!~bazbaz@baz.baz joins
 insert servermsglogged [% (MSG)=:baz!~bazbaz@baz.baz%JOIN%:#ch_win3]
 log 1 $ channels:#ch_win3:user_ids set to: [1], [2], [3], [4], [me]
 log 3 $ baz!~bazbaz@baz.baz joins
@@ -103,34 +93,27 @@ log 1 $ channels:#ch_win3:modes_listy:l set to: [foo]
 insert servermsg-mode [% (ARGS)=+l%:bar]
 log 1 $ channels:#ch_win3:modes_listy:l set to: [bar], [foo]
 insert servermsg-mode [% (ARGS)=+l-l%:baz%foo]
 insert servermsg-mode [% (ARGS)=+l%:bar]
 log 1 $ channels:#ch_win3:modes_listy:l set to: [bar], [foo]
 insert servermsg-mode [% (ARGS)=+l-l%:baz%foo]
-log 1 $ channels:#ch_win3:modes_listy:l set to: [bar], [baz], [foo]
 log 1 $ channels:#ch_win3:modes_listy:l set to: [bar], [baz]
 
 # check server setting type-B modes
 insert servermsg-mode [% (ARGS)=+k%:password]
 log 1 $ channels:#ch_win3:modes_valued:k set to: [password]
 insert servermsg-mode [% (ARGS)=-k%:*]
 log 1 $ channels:#ch_win3:modes_listy:l set to: [bar], [baz]
 
 # check server setting type-B modes
 insert servermsg-mode [% (ARGS)=+k%:password]
 log 1 $ channels:#ch_win3:modes_valued:k set to: [password]
 insert servermsg-mode [% (ARGS)=-k%:*]
-log 1 $ channels:#ch_win3:modes_valued:k deleted
+log 1 $ channels:#ch_win3:modes_valued cleared
 
 # check server setting type-C modes
 insert servermsg-mode [% (ARGS)=+x%:foo]
 log 1 $ channels:#ch_win3:modes_valued:x set to: [foo]
 
 # check server setting type-C modes
 insert servermsg-mode [% (ARGS)=+x%:foo]
 log 1 $ channels:#ch_win3:modes_valued:x set to: [foo]
-insert servermsg-mode [% (ARGS)=+x%:bar]
+insert servermsg-mode [% (ARGS)=+xy%:bar%zab]
 log 1 $ channels:#ch_win3:modes_valued:x set to: [bar]
 log 1 $ channels:#ch_win3:modes_valued:x set to: [bar]
-insert servermsg-mode [% (ARGS)=+y%:zab]
 log 1 $ channels:#ch_win3:modes_valued:y set to: [zab]
 insert servermsg-mode [(ARGS)=-x]
 log 1 $ channels:#ch_win3:modes_valued:x deleted
 
 # check server setting type-D modes
 insert servermsg-mode [(ARGS)=+aBc]
 log 1 $ channels:#ch_win3:modes_valued:y set to: [zab]
 insert servermsg-mode [(ARGS)=-x]
 log 1 $ channels:#ch_win3:modes_valued:x deleted
 
 # check server setting type-D modes
 insert servermsg-mode [(ARGS)=+aBc]
-log 1 $ channels:#ch_win3:modes_toggled set to: [a]
-log 1 $ channels:#ch_win3:modes_toggled set to: [Ba]
-log 1 $ channels:#ch_win3:modes_toggled set to: [Bac]
+log 1 $ channels:#ch_win3:modes_toggled set to: [B], [a], [c]
 insert servermsg-mode [(ARGS)=-cba]
 insert servermsg-mode [(ARGS)=-cba]
-log 1 $ channels:#ch_win3:modes_toggled set to: [Ba]
 log 1 $ channels:#ch_win3:modes_toggled set to: [B]
 insert servermsg-mode [(ARGS)=-B+cb]
 log 1 $ channels:#ch_win3:modes_toggled set to: [B]
 insert servermsg-mode [(ARGS)=-B+cb]
-log 1 $ channels:#ch_win3:modes_toggled set to: []
-log 1 $ channels:#ch_win3:modes_toggled set to: [c]
-log 1 $ channels:#ch_win3:modes_toggled set to: [bc]
+log 1 $ channels:#ch_win3:modes_toggled set to: [b], [c]
index b33fbcb3a7bbbdd00a2ebe4193db94307dccf2e7..759e0a91355d0a33c21b7abf3af9bac1969a8974 100644 (file)
@@ -117,7 +117,8 @@ insert servermsglogged [% (MSG)=:foo.bar.baz%332%foo%#ch_win4%:foo%bar%baz]
 insert servermsglogged [% (MSG)=:foo.bar.baz%333%foo%#ch_win4%baz!~bazbaz@OLD.baz.baz%1234567890]
 insert topic-set-to [% baz.baz=OLD.baz.baz (WIN_ID)=4 (TOPIC)=foo%bar%baz]
 insert join-channel-0-1-list-residents [% (WIN_ID)=4 (RESIDENT_NAMES)=foo%baz]
 insert servermsglogged [% (MSG)=:foo.bar.baz%333%foo%#ch_win4%baz!~bazbaz@OLD.baz.baz%1234567890]
 insert topic-set-to [% baz.baz=OLD.baz.baz (WIN_ID)=4 (TOPIC)=foo%bar%baz]
 insert join-channel-0-1-list-residents [% (WIN_ID)=4 (RESIDENT_NAMES)=foo%baz]
-insert join-channel-1-end-of-names [% (WIN_ID)=4 (RESIDENT_IDS)=[2],%[me] (RESIDENT_NICKS)=baz,%foo]
+insert join-channel-1-end-of-names range=:-3 [(WIN_ID)=4]
+insert join-channel-1-end-of-names range=-2: [% (WIN_ID)=4 (RESIDENT_IDS)=[2],%[me] (RESIDENT_NICKS)=baz,%foo]
 
 # check _observed_ topic change _does_ affect users database, and …
 insert servermsglogged [% (MSG)=:baz!~bazbaz@baz.baz%TOPIC%#ch_win4%:foo%bar%baz]
 
 # check _observed_ topic change _does_ affect users database, and …
 insert servermsglogged [% (MSG)=:baz!~bazbaz@baz.baz%TOPIC%#ch_win4%:foo%bar%baz]
index ea1a5aecb1e386d46353d0627266ae09cb814098..c04d99ac1a37582fd473283432e6b931bd6f872d 100644 (file)
@@ -78,18 +78,20 @@ insert join-empty [(WIN_ID)=6 #ch_win=$ch_win]
 # test effect of PREFIX
 insert join-channel-0-cmd-to-list-residents [% (WIN_ID)=7 (RESIDENT_NAMES)=foo%@bar%+baz%=quux]
 insert user-set-to range=:1 [(USER_ID)=1 (USER_NICK)=bar]
 # test effect of PREFIX
 insert join-channel-0-cmd-to-list-residents [% (WIN_ID)=7 (RESIDENT_NAMES)=foo%@bar%+baz%=quux]
 insert user-set-to range=:1 [(USER_ID)=1 (USER_NICK)=bar]
-log 1 $ channels:#ch_win7:modes_listy:o set to: [1]
 insert user-set-to range=:1 [(USER_ID)=2 (USER_NICK)=baz]
 insert user-set-to range=:1 [(USER_ID)=2 (USER_NICK)=baz]
-log 1 $ channels:#ch_win7:modes_listy:v set to: [2]
 insert user-set-to range=:1 [(USER_ID)=3 (USER_NICK)==quux]
 insert user-set-to range=:1 [(USER_ID)=3 (USER_NICK)==quux]
-insert join-channel-1-end-of-names [% (WIN_ID)=7 (RESIDENT_IDS)=[1],%[2],%[3],%[me] (RESIDENT_NICKS)=@bar,%+baz,%=quux,%foo]
+insert join-channel-1-end-of-names range=:-3 [(WIN_ID)=7]
+log 1 $ channels:#ch_win7:modes_listy:o set to: [1]
+log 1 $ channels:#ch_win7:modes_listy:v set to: [2]
+insert join-channel-1-end-of-names range=-2: [% (WIN_ID)=7 (RESIDENT_IDS)=[1],%[2],%[3],%[me] (RESIDENT_NICKS)=@bar,%+baz,%=quux,%foo]
 insert isupport-set [(KEY)=PREFIX (VALUE)=(vE)+=]
 insert join-channel-0-cmd-to-list-residents [% (WIN_ID)=8 (RESIDENT_NAMES)=foo%@bar%+baz%=quux]
 insert user-set-to range=:1 [(USER_ID)=4 (USER_NICK)=@bar]
 insert isupport-set [(KEY)=PREFIX (VALUE)=(vE)+=]
 insert join-channel-0-cmd-to-list-residents [% (WIN_ID)=8 (RESIDENT_NAMES)=foo%@bar%+baz%=quux]
 insert user-set-to range=:1 [(USER_ID)=4 (USER_NICK)=@bar]
-log 1 $ channels:#ch_win8:modes_listy:v set to: [2]
 insert user-set-to range=:1 [(USER_ID)=5 (USER_NICK)=quux]
 insert user-set-to range=:1 [(USER_ID)=5 (USER_NICK)=quux]
+insert join-channel-1-end-of-names range=:-3 [(WIN_ID)=8]
+log 1 $ channels:#ch_win8:modes_listy:v set to: [2]
 log 1 $ channels:#ch_win8:modes_listy:E set to: [5]
 log 1 $ channels:#ch_win8:modes_listy:E set to: [5]
-insert join-channel-1-end-of-names [% (WIN_ID)=8 (RESIDENT_IDS)=[2],%[4],%[5],%[me] (RESIDENT_NICKS)=+baz,%@bar,%=quux,%foo]
+insert join-channel-1-end-of-names range=-2: [% (WIN_ID)=8 (RESIDENT_IDS)=[2],%[4],%[5],%[me] (RESIDENT_NICKS)=+baz,%@bar,%=quux,%foo]
 
 # test effect of USERLEN
 insert join-empty [(WIN_ID)=9]
 
 # test effect of USERLEN
 insert join-empty [(WIN_ID)=9]
index 6dfdb7bf5542607621a099428c92e9537247099b..926e5a7289a4b86434d4901af4fdf1d2a6b56bb2 100644 (file)
@@ -14,5 +14,6 @@ insert join-channel-0-1-list-residents
 
 × join-channel-1-end-of-names
 insert servermsglogged [% (MSG)=:foo.bar.baz%366%foo%#ch_win(WIN_ID)%:End%of%/NAMES%list.]
 
 × join-channel-1-end-of-names
 insert servermsglogged [% (MSG)=:foo.bar.baz%366%foo%#ch_win(WIN_ID)%:End%of%/NAMES%list.]
+log 1 $ channels:#ch_win(WIN_ID):modes_listy cleared
 log 1 $ channels:#ch_win(WIN_ID):user_ids set to: (RESIDENT_IDS)
 log (WIN_ID) $ residents: (RESIDENT_NICKS)
 log 1 $ channels:#ch_win(WIN_ID):user_ids set to: (RESIDENT_IDS)
 log (WIN_ID) $ residents: (RESIDENT_NICKS)
index 897a791eb2f4bd8a06200c025c7bc68efbd746b1..47043fffba00de1636298df163b3620244a39101 100644 (file)
@@ -115,8 +115,9 @@ log 3 $ foo1!~foofoo@foo.foo renames foo
 # join channel with other user
 insert join-channel-0-cmd-to-list-residents [% (WIN_ID)=4 (RESIDENT_NAMES)=foo%@baz]
 insert user-set-to range=:1 [(USER_ID)=1 (USER_NICK)=baz]
 # join channel with other user
 insert join-channel-0-cmd-to-list-residents [% (WIN_ID)=4 (RESIDENT_NAMES)=foo%@baz]
 insert user-set-to range=:1 [(USER_ID)=1 (USER_NICK)=baz]
+insert join-channel-1-end-of-names range=:-3 [(WIN_ID)=4]
 log 1 $ channels:#ch_win4:modes_listy:o set to: [1]
 log 1 $ channels:#ch_win4:modes_listy:o set to: [1]
-insert join-channel-1-end-of-names [% (WIN_ID)=4 (RESIDENT_IDS)=[1],%[me] (RESIDENT_NICKS)=@baz,%foo]
+insert join-channel-1-end-of-names range=-2: [% (WIN_ID)=4 (RESIDENT_IDS)=[1],%[me] (RESIDENT_NICKS)=@baz,%foo]
 
 # process non-self channel JOIN
 insert servermsglogged [% (MSG)=:bar!~barbar@bar.bar%JOIN%:#ch_win4]
 
 # process non-self channel JOIN
 insert servermsglogged [% (MSG)=:bar!~barbar@bar.bar%JOIN%:#ch_win4]