home · contact · privacy
Restructure topic setting.
authorChristian Heller <c.heller@plomlompom.de>
Tue, 2 Sep 2025 17:40:12 +0000 (19:40 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Tue, 2 Sep 2025 17:40:12 +0000 (19:40 +0200)
ircplom/client.py
ircplom/client_tui.py

index ce55b04c3f12ce6676df7e23a5cb46afc4c97624..b1fd4b8b20516de71adabf6241a4c60b2b0a9329 100644 (file)
@@ -139,6 +139,8 @@ class _UpdatingMixin(AutoAttrMixin):
             return None
         if isinstance(self, _CompletableStringsList):
             return self.completed
+        if isinstance(self, _Topic):
+            return self.into_parent()
         for cls in [cls for cls in self.__class__.__mro__
                     if AutoAttrMixin not in cls.__mro__]:
             obj = cls()
@@ -304,9 +306,72 @@ class _IrcConnection(BaseIrcConnection, _ClientIdMixin):
                                     client_id=self.client_id).kw(e=e)
 
 
+_TopicWho = TypeVar('_TopicWho', bound=NickUserHost)
+
+
+class Topic[_TopicWho]:
+    'Encapsulates content and setter of channel topic.'
+    _what = ''
+    _who: Optional[_TopicWho] = None
+
+    def __eq__(self, other) -> bool:
+        return isinstance(self, Topic) and isinstance(other, Topic)\
+                and self.what == other.what\
+                and self.who == other.who
+
+    def _set_what(self, what: str) -> None:
+        pass
+
+    def _set_who(self, who: _TopicWho) -> None:
+        pass
+
+    @property
+    def what(self) -> str:
+        'Content of channel topic.'
+        return self._what[:]
+
+    @what.setter
+    def what(self, what: str) -> None:
+        self._set_what(what)
+
+    @property
+    def who(self) -> Optional[_TopicWho]:
+        'Setter of channel topic.'
+        return self._who
+
+    @who.setter
+    def who(self, who: _TopicWho) -> None:
+        self._set_who(who)
+
+
+class _Topic(Topic['_NickUserHost']):
+
+    def __init__(self) -> None:
+        self.completed: tuple[str, Optional['_NickUserHost']] = ('', None)
+
+    def _set_what(self, what: str) -> None:
+        self._what = what
+        self._who = None
+
+    def _set_who(self, who: '_NickUserHost') -> None:
+        self._who = who.copy()
+        self.complete()
+
+    def complete(self) -> None:
+        'Declare data collection finished, return full or empty content.'
+        self.completed = (('', None) if self._who is None
+                          else (self._what, self._who.copy()))
+
+    def into_parent(self) -> Topic:
+        'Transform into as presentable as impotent parent class variant.'
+        obj = Topic[NickUserHost]()
+        obj._what, obj._who = self.completed
+        return obj
+
+
 class _Channel:
     user_ids: _CompletableStringsList
-    topic: tuple[str, str] = ('', '')
+    topic: _Topic
 
     def __init__(self,
                  get_id_for_nick: Callable,
@@ -373,13 +438,25 @@ class _NickUserHost(NickUserHost):
                         )[-1]
         return name + str(0 if not digits else (int(digits) + 1))
 
+    def copy(self) -> Self:
+        'Create indepedentent copy.'
+        return self.from_str(str(self))
+
 
 class _UpdatingServerCapability(_UpdatingMixin, ServerCapability):
     pass
 
 
+class _UpdatingTopic(_UpdatingMixin, _Topic):
+
+    def complete(self) -> None:
+        super().complete()
+        self._on_update()
+
+
 class _UpdatingChannel(_UpdatingMixin, _Channel):
     user_ids: _UpdatingCompletableStringsList
+    topic: _UpdatingTopic
 
 
 class _UpdatingNickUserHost(_UpdatingMixin, _NickUserHost):
@@ -691,11 +768,9 @@ class Client(ABC, ClientQueueMixin):
                     key, data = _Dict.key_val_from_eq_str(item)
                     self._db.isupport[key] = data
         elif ret['verb'] == '332':  # RPL_TOPIC
-            self._db.channels[ret['channel']].topic = (ret['topic'], '')
+            self._db.channels[ret['channel']].topic.what = ret['topic']
         elif ret['verb'] == '333':  # RPL_TOPICWHOTIME
-            self._db.channels[ret['channel']].topic = (
-                    self._db.channels[ret['channel']].topic[0],
-                    str(ret['author']))
+            self._db.channels[ret['channel']].topic.who = ret['author']
         elif ret['verb'] == '353':  # RPL_NAMREPLY
             self._db.channels[ret['channel']].add_from_namreply(ret['names'])
         elif ret['verb'] == '366':  # RPL_ENDOFNAMES
@@ -765,8 +840,8 @@ class Client(ABC, ClientQueueMixin):
                 self._log(f'{ret["quitter"]} quits: {ret["message"]}',
                           LogScope.CHAT, target=ch_name)
         elif ret['verb'] == 'TOPIC':
-            self._db.channels[ret['channel']].topic = (ret['topic'],
-                                                       str(ret['author']))
+            self._db.channels[ret['channel']].topic.what = ret['topic']
+            self._db.channels[ret['channel']].topic.who = ret['author']
 
 
 ClientsDb = dict[str, Client]
index d7085d86ac052a3f0be35d04a925a3f640199668..329958e1e2d46147d0659e5c7454cc309c106078 100644 (file)
@@ -11,7 +11,7 @@ from ircplom.irc_conn import IrcMessage
 from ircplom.client import (
         AutoAttrMixin, Client, ClientQueueMixin, Dict, DictItem, IrcConnSetup,
         LogScope, NewClientEvent, NickUserHost, ServerCapability,
-        SharedClientDbFields)
+        SharedClientDbFields, Topic)
 
 CMD_SHORTCUTS['disconnect'] = 'window.disconnect'
 CMD_SHORTCUTS['join'] = 'window.join'
@@ -191,14 +191,14 @@ class _ChannelWindow(_ChatWindow):
 
 class _UpdatingChannel(_UpdatingNode):
     user_ids: tuple[str, ...] = tuple()
-    topic: tuple[str, str] = ('', '')
+    topic: Topic[NickUserHost] = Topic()
     log_scopes = {tuple(): LogScope.CHAT}
 
     def set_and_check_for_change(self, update: _Update
                                  ) -> Optional[tuple[LogScope, Any]]:
-        assert isinstance(update.value, tuple)
-        msg: str | dict[str, tuple[str, ...]] = {}
+        msg: str | dict[str, tuple[str, ...]]
         if update.path == ('user_ids',):
+            assert isinstance(update.value, tuple)
             msg = {}
             if not self.user_ids:
                 msg['nicks:residents'] = tuple(update.value)
@@ -207,11 +207,9 @@ class _UpdatingChannel(_UpdatingNode):
                                             if id_ not in self.user_ids)
                 msg['nuhs:parting'] = tuple(id_ for id_ in self.user_ids
                                             if id_ not in update.value)
-        elif update.path == ('topic',):
-            if '' in update.value:
-                return None
-            msg = f'raw:topic set by {update.value[1]} to: {update.value[0]}'
         if super().set_and_check_for_change(update):
+            if update.path == ('topic',):
+                msg = f'raw:{self.topic.who} set topic to: {self.topic.what}'
             return (self._scope(update.path), msg)
         return None