home · contact · privacy
Replace complexity of UpdatingNode.recursive_set_and_report_change by more state...
authorChristian Heller <c.heller@plomlompom.de>
Fri, 19 Sep 2025 12:28:21 +0000 (14:28 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Fri, 19 Sep 2025 12:28:21 +0000 (14:28 +0200)
ircplom/client_tui.py

index 74d905896323c5771dfd48dbebae5ed9f59dd1bd..3e577f45b782048dafd4bbe79577b0eb5f487adf 100644 (file)
@@ -1,7 +1,7 @@
 'TUI adaptions to Client.'
 # built-ins
 from getpass import getuser
-from typing import Any, Callable, Optional, Self, Sequence
+from typing import Any, Callable, Optional, Sequence
 # ourselves
 from ircplom.tui_base import (BaseTui, PromptWidget, TuiEvent, Window,
                               CMD_SHORTCUTS)
@@ -24,21 +24,25 @@ _LOG_PREFIX_IN = '<'
 
 
 class _Update:
+    old_value: Any
+    results: list[tuple[LogScope, Any]]
 
-    def __init__(self, path: tuple[str, ...], value: Any, force_log=False
-                 ) -> None:
-        self.path = path
+    def __init__(self, path: tuple[str, ...], value: Any) -> None:
+        self.full_path = path
+        self.rel_path = self.full_path[:]
         self.value = value
-        self.force_log = force_log
+        self.old_value = None
+        self.force_log = False
+        self.results = []
 
     @property
     def key(self) -> str:
         'Name of item or attribute to be processed.'
-        return self.path[0]
+        return self.rel_path[0]
 
-    def decremented(self) -> Self:
-        'Create copy with .path reduced by leftmost step.'
-        return self.__class__(self.path[1:], self.value, self.force_log)
+    def decrement_path(self) -> None:
+        'Remove first element from .rel_path.'
+        self.rel_path = self.rel_path[1:]
 
 
 class _UpdatingNode(AutoAttrMixin):
@@ -55,28 +59,26 @@ class _UpdatingNode(AutoAttrMixin):
                 scopes = c.log_scopes | scopes
         return scopes.get(key, scopes[''])
 
-    def recursive_set_and_report_change(
-            self, update: _Update) -> tuple[tuple[LogScope, Any], ...]:
-        'Apply update, return if that actually made a difference.'
+    def recursive_set_and_report_change(self, update: _Update) -> None:
+        'Apply update, and, if it makes a difference, add to its .results.'
         update.force_log = update.force_log or (not self._is_set(update.key))
         node = self._get(update.key)
-        if len(update.path) > 1:
-            return node.recursive_set_and_report_change(update.decremented())
-        return self._focused_set_and_report_change(node, update)
-
-    def _focused_set_and_report_change(
-            self, old_value: '_UpdatingNode', update: _Update
-            ) -> tuple[tuple[LogScope, Any], ...]:
-        scope = self._scope(update.key)
-        do_report = update.force_log
-        if update.value is None:
-            if self._is_set(update.key):
-                self._unset(update.key)
+        if len(update.rel_path) > 1:
+            update.decrement_path()
+            node.recursive_set_and_report_change(update)
+        else:
+            update.old_value = node
+            scope = self._scope(update.key)
+            do_report = update.force_log
+            if update.value is None:
+                if self._is_set(update.key):
+                    self._unset(update.key)
+                    do_report |= True
+            elif update.old_value != update.value:
+                self._set(update.key, update.value)
                 do_report |= True
-        elif old_value != update.value:
-            self._set(update.key, update.value)
-            do_report |= True
-        return ((scope, update.value),) if do_report else tuple()
+            if do_report:
+                update.results += [(scope, update.value)]
 
     def _get(self, key: str) -> Any:
         return getattr(self, key)
@@ -213,58 +215,46 @@ class _UpdatingChannel(_UpdatingNode, Channel):
     log_scopes = {'': LogScope.CHAT}
     user_ids: set[str]
 
-    def _focused_set_and_report_change(
-            self, old_value: '_UpdatingNode', update: _Update
-            ) -> tuple[tuple[LogScope, Any], ...]:
+    def recursive_set_and_report_change(self, update: _Update) -> None:
+        super().recursive_set_and_report_change(update)
         if update.key == 'user_ids':
-            assert isinstance(update.value, set)
-            d = ({'NUHS:joining': tuple(sorted(id_ for id_ in update.value
-                                               if id_ not in self.user_ids))}
-                 if self.user_ids
-                 else {'NICKS:residents': tuple(sorted(update.value))})
-            if super()._focused_set_and_report_change(old_value, update):
-                return ((self._scope(update.key), d),)
-            return tuple()
-        return super()._focused_set_and_report_change(old_value, update)
+            if update.old_value:
+                d = {'NUHS:joining': tuple(sorted(
+                        id_ for id_ in update.value
+                        if id_ not in update.old_value))}
+            else:
+                d = {'NICKS:residents': tuple(sorted(update.value))}
+            update.results = [(self._scope(update.key), d)]
 
 
 class _UpdatingUser(_UpdatingNode, User):
     log_scopes = {'exit_msg': LogScope.USER}
     prev_nick = '?'
 
-    def _focused_set_and_report_change(
-            self, old_value: '_UpdatingNode', update: _Update
-            ) -> tuple[tuple[LogScope, Any], ...]:
-        assert isinstance(update.value, str)
-        if (result := super()._focused_set_and_report_change(old_value,
-                                                             update)):
-            if update.key not in {'nick', 'exit_msg'}:
-                return result
+    def recursive_set_and_report_change(self, update: _Update) -> None:
+        super().recursive_set_and_report_change(update)
+        if update.key in {'nick', 'exit_msg'}:
             msg = 'RAW:'
             if update.key == 'nick':
-                if self.prev_nick != '?':
-                    result = result + (
-                            (LogScope.USER,
-                             msg + f'{self.prev} renames {update.value}'),)
-                return result
-            if update.key == 'exit_msg' and update.value:
-                msg += f'{self} '
-                msg += 'quits' if update.value[0] == 'Q' else 'parts'
-                if len(update.value) > 1:
-                    msg += ': ' + update.value[1:]
-                return ((self._scope(update.key), msg),)
-        return tuple()
+                self.prev_nick = update.old_value
+                if update.old_value != '?':
+                    update.results += [
+                        (LogScope.USER,
+                         msg + f'{self.prev} renames {update.value}')]
+            elif update.key == 'exit_msg':
+                update.results.clear()
+                if update.value:
+                    msg += f'{self} '
+                    msg += 'quits' if update.value[0] == 'Q' else 'parts'
+                    if len(update.value) > 1:
+                        msg += ': ' + update.value[1:]
+                    update.results += [(self._scope(update.key), msg)]
 
     @property
     def prev(self) -> str:
         'Return .nickuserhost with .prev_nick as .nick.'
         return str(NickUserHost(self.prev_nick, self.user, self.host))
 
-    def __setattr__(self, key: str, value) -> None:
-        if key == 'nick':
-            self.prev_nick = self.nick
-        super().__setattr__(key, value)
-
 
 class _UpdatingServerCapability(_UpdatingNode, ServerCapability):
     pass
@@ -345,15 +335,15 @@ class _ClientWindowsManager:
 
     def update_db(self, update: _Update) -> bool:
         'Apply update to .db, and if changing anything, log and trigger.'
-        result = self.db.recursive_set_and_report_change(update)
-        if not result:
+        self.db.recursive_set_and_report_change(update)
+        if not update.results:
             return False
-        for t in result:
+        for t in update.results:
             scope, value = t
-            log_path = ':'.join(update.path)
+            log_path = ':'.join(update.full_path)
             log_kwargs: dict[str, Any] = {'scope': scope}
             if scope in {LogScope.CHAT, LogScope.USER}:
-                log_kwargs |= {'target': update.path[1]}
+                log_kwargs |= {'target': update.full_path[1]}
             if value is None:
                 self.log(f'{log_path} cleared', **log_kwargs)
             elif isinstance(value, Topic):