From f4c04163984642925a7c0e1a79b97f2e7eeeb1c3 Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 3 Jun 2019 09:29:12 +0000 Subject: [PATCH] Add in-memory message supply --- cheuph/exceptions.py | 9 +-- cheuph/message.py | 7 ++- cheuph/message_supply.py | 123 +++++++++++++++++++++++++++++++++++---- 3 files changed, 118 insertions(+), 21 deletions(-) diff --git a/cheuph/exceptions.py b/cheuph/exceptions.py index 7bef822..5c990ae 100644 --- a/cheuph/exceptions.py +++ b/cheuph/exceptions.py @@ -1,9 +1,4 @@ -__all__ = ["ElementException", "TreeException"] +__all__ = ["MessageSupplyException"] - -class ElementException(Exception): - pass - - -class TreeException(Exception): +class MessageSupplyException(Exception): pass diff --git a/cheuph/message.py b/cheuph/message.py index 5024171..593a149 100644 --- a/cheuph/message.py +++ b/cheuph/message.py @@ -1,6 +1,6 @@ import datetime from dataclasses import dataclass -from typing import Hashable, List +from typing import Hashable, List, Optional from .attributed_lines import AttributedLines from .markup import AT, AttributedText @@ -25,7 +25,8 @@ class Message: truncation status. """ - message_id: Id + id: Id + parent_id: Optional[Id] time: datetime.datetime nick: str content: str @@ -42,7 +43,7 @@ class Message: result.append(nick + AT(lines[0])) result.extend(nick_spaces + AT(line) for line in lines[1:]) - return RenderedMessage(self.message_id, meta, result) + return RenderedMessage(self.id, meta, result) @dataclass class RenderedMessage: diff --git a/cheuph/message_supply.py b/cheuph/message_supply.py index 7e70111..823a8c0 100644 --- a/cheuph/message_supply.py +++ b/cheuph/message_supply.py @@ -1,11 +1,13 @@ -from typing import List, Optional +import abc +from typing import Dict, List, Optional +from .exceptions import MessageSupplyException from .message import Id, Message -__all__ = ["MessageSupply"] +__all__ = ["MessageSupply", "InMemoryMessageSupply"] -class MessageSupply: +class MessageSupply(abc.ABC): """ A MessageSupply holds all of a room's known messages. It can be queried in different ways. Messages can also be added to or removed from the MessageSupply @@ -16,24 +18,123 @@ class MessageSupply: similar. """ - # TODO should throw exception if it can't find the message + @abc.abstractmethod def get(self, message_id: Id) -> Message: - pass # TODO + pass + @abc.abstractmethod def children_ids(self, message_id: Id) -> List[Id]: - pass # TODO + pass + @abc.abstractmethod + def sibling_ids(self, message_id: Id) -> List[Id]: + pass + + @abc.abstractmethod def parent_id(self, message_id: Id) -> Optional[Id]: - pass # TODO + pass def oldest_ancestor_id(self, message_id: Id) -> Id: - pass # TODO + ancestor_id = message_id + + while True: + parent_id = self.parent_id(ancestor_id) + if parent_id is None: break + ancestor_id = parent_id + + return ancestor_id def previous_id(self, message_id: Id) -> Optional[Id]: - pass # TODO + sibling_ids = self.sibling_ids(message_id) + + try: + i = sibling_ids.index(message_id) + if i <= 0: + return None + else: + return sibling_ids[i - 1] + except ValueError: + return None def next_id(self, message_id: Id) -> Optional[Id]: - pass # TODO + sibling_ids = self.sibling_ids(message_id) + + try: + i = sibling_ids.index(message_id) + if i >= len(sibling_ids) - 1: + return None + else: + return sibling_ids[i + 1] + except ValueError: + return None + + @abc.abstractmethod + def lowest_root_id(self) -> Optional[Id]: + pass + +class InMemoryMessageSupply(MessageSupply): + """ + This message supply stores messages in memory. It orders the messages by + their ids. + """ + + def __init__(self) -> None: + self._messages: Dict[Id, Message] = {} + self._children: Dict[Id, List[Message]] = {} + + def add(self, message: Message) -> None: + if message.id in self._messages: + self.remove(message.id) + + self._messages[message.id] = message + + if message.parent_id is not None: + children = self._children.get(message.parent_id, []) + children.append(message) + children.sort(key=lambda m: m.id) + self._children[message.parent_id] = children + + def remove(self, message_id: Id) -> None: + message = self._messages.get(message_id) + if message is None: return + + self._messages.pop(message) + + if message.parent_id is not None: + children = self._children.get(message.id) + if children is not None: # just to satisfy mypy + children.remove(message) + + if not children: + self._children.pop(message.id) + + def get(self, message_id: Id) -> Message: + message = self._messages.get(message_id) + + if message is None: + raise MessageSupplyException( + f"message with id {message_id!r} does not exist") + + return message + + def child_ids(self, message_id: Id) -> List[Id]: + return [m.id for m in self._children.get(message_id, [])] + + def parent_id(self, message_id: Id) -> Optional[Id]: + message = self.get(message_id) + return message.parent_id + + def sibling_ids(self, message_id: Id) -> List[Id]: + parent_id = self.parent_id(message_id) + + if parent_id is None: + roots = [m for m in self._messages.values() if m.parent_id is None] + sibling_ids = list(sorted(root.id for root in roots)) + else: + sibling_ids = self.children_ids(parent_id) + + return sibling_ids def lowest_root_id(self) -> Optional[Id]: - pass # TODO + roots = list(sorted(self._messages.keys())) + return roots[-1] if roots else None