bowl/cheuph/render/element.py

163 lines
4.3 KiB
Python

import abc
from typing import Hashable, List, Optional
from .exceptions import ElementException, TreeException
from .markup import AttributedText
__all__ = ["Id", "Element", "ElementSupply", "RenderedElement"]
Id = Hashable
class Element(abc.ABC):
def __init__(self,
id: Id,
parent_id: Optional[Id],
children: Optional[List[Element]],
) -> None:
self._id = id
self._parent_id = id
self._children = children
@property
def id(self) -> Id:
return self._id
@property
def parent_id(self) -> Id:
return self._parent_id
@property
def children(self) -> List[Element]:
if self._children is None:
raise ElementException("Element doesn't know its children")
return self._children
@children.setter
def children(self, children: List[Element]) -> None:
self._children = children
@abc.abstractmethod
def render(self,
width: int,
depth: int,
highlighted: bool = False,
folded: bool = False,
) -> RenderedElement:
pass
class ElementSupply(abc.ABC):
@abc.abstractmethod
def get(self, element_id: Id) -> Element:
pass
@abc.abstractmethod
def get_parent_id(self, element_id: Id) -> Optional[Id]:
pass
def get_parent(self, element_id: Id) -> Optional[Element]:
parent_id = self.get_parent_id(element_id)
if parent_id is not None:
return self.get(parent_id)
else:
return None
@abc.abstractmethod
def get_children_ids(self, element_id: Id) -> List[Id]:
pass
def get_children(self, element_id: Id) -> List[Element]:
children_ids = self.get_children_ids(element_id)
children: List[Element] = []
for child_id in children_ids:
children.append(self.get(child_id))
return children
# There is not a clear-cut way to get the previous or next "sibling" of an
# element that is itself a child of the implicit root (None), since
# get_children() doesn't accept None in its element_id argument.
#
# Because of this, the get_previous_id() and get_next_id() functions are
# abstract (until I decide to change the signature of get_children(), that
# is :P).
@abc.abstractmethod
def get_previous_id(self, element_id: Id) -> Optional[Id]:
pass
def get_previous(self, element_id: Id) -> Optional[Element]:
previous_id = self.get_previous_id(element_id)
if previous_id is not None:
return self.get(previous_id)
else:
return None
@abc.abstractmethod
def get_next_id(self, element_id: Id) -> Optional[Id]:
pass
def get_next(self, element_id: Id) -> Optional[Element]:
next_id = self.get_next_id(element_id)
if next_id is not None:
return self.get(next_id)
else:
return None
def get_furthest_ancestor_id(self,
element_id: Id,
root_id: Optional[Id] = None,
) -> Id:
current_id = element_id
while True:
parent_id = self.get_parent_id(current_id)
if parent_id == root_id:
return current_id
elif parent_id is None:
raise TreeException("Reached implicit root before hitting specified root")
current_id = parent_id
def get_furthest_ancestor(self,
element_id: Id,
root_id: Optional[Id] = None,
) -> Element:
return self.get(self.get_furthest_ancestor_id(element_id,
root_id=root_id))
def get_tree(self, tree_id: Id) -> Element:
tree = self.get(tree_id)
children: List[Element] = []
for child_id in self.get_children_ids(tree_id):
children.append(self.get_tree(child_id))
tree.children = children
return tree
class RenderedElement:
def __init__(self,
element: Element,
rendered: List[AttributedText],
) -> None:
self._element = element
self._lines = rendered
@property
def element(self) -> Element:
return self._element
@property
def lines(self) -> List[AttributedText]:
return self._lines
@property
def height(self) -> int:
return len(self._lines)