Skip to content

Commit

Permalink
Inheritance refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
Hrabal committed Nov 22, 2020
1 parent 4f0a534 commit 0a8d250
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 166 deletions.
21 changes: 20 additions & 1 deletion tempy/bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,23 @@


class TempyClass:
pass
_FORMAT_ATTRS = {
"style": lambda x: " ".join("%s: %s;" % (k, v) for k, v in x.items()),
"klass": " ".join,
"comment": lambda x: x,
}
_SPECIAL_ATTRS = {"klass": "class", "typ": "type", "_for": "for", "_async": "async"}
_TO_SPECIALS = {v: k for k, v in _SPECIAL_ATTRS.items()}
_MAPPING_ATTRS = ("style",)
_SET_VALUES_ATTRS = ("klass",)

def __init__(self, **kwargs):
self.parent = None
self.childs = []
self._name = None
self.root = None
self.attrs = {}
self._depth = 0
self._from_factory = False
self._own_index = 0
self.content_data = kwargs
11 changes: 0 additions & 11 deletions tempy/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,6 @@ class Tag(DOMElement):
"""
_template = "%s<%s%s>%s%s</%s>"
_void = False
_MAPPING_ATTRS = ("style",)
_SET_VALUES_ATTRS = ("klass",)
_SPECIAL_ATTRS = {"klass": "class", "typ": "type", "_for": "for", "_async": "async"}
_TO_SPECIALS = {v: k for k, v in _SPECIAL_ATTRS.items()}
_FORMAT_ATTRS = {
"style": lambda x: " ".join("%s: %s;" % (k, v) for k, v in x.items()),
"klass": " ".join,
"comment": lambda x: x,
}

def __init__(self, *args, **kwargs):
data = kwargs.pop("data", {})
Expand Down Expand Up @@ -113,7 +104,6 @@ def remove_class(self, cssclass):

def css(self, *props, **kwprops):
"""Adds css properties to this element."""
self._stable = False
if props:
if len(props) == 1 and isinstance(props[0], Mapping):
styles = props[0]
Expand Down Expand Up @@ -141,7 +131,6 @@ def show(self, display=None):

def toggle(self):
"""Same as jQuery's toggle, toggles the display attribute of this element."""
self._stable = False
return self.show() if self.attrs["style"]["display"] == "none" else self.hide()

def html(self, pretty=False):
Expand Down
177 changes: 91 additions & 86 deletions tempy/modifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
# @author: Federico Cerchiari <federicocerchiari@gmail.com>
"""Classes for DOM building"""
from collections import Iterable
from copy import copy

from .bases import TempyClass
from .tools import content_receiver
from .exceptions import TagError, WrongArgsError, DOMModByKeyError, DOMModByIndexError


class BaseDOMModifier:
class BaseDOMModifier(TempyClass):
def _insert(self, dom_group, idx=None, prepend=False, name=None):
"""Inserts a DOMGroup inside this element.
If provided at the given index, if prepend at the start of the childs list, by default at the end.
Expand Down Expand Up @@ -39,8 +40,94 @@ def __call__(self, _, child, name=None):
"""Calling the object will add the given parameters as childs"""
self._insert(child, name=name)

def clone(self):
"""Returns a deepcopy of this element."""
return copy(self)

class OperatorsModifier(BaseDOMModifier):

class SiblingsManager(BaseDOMModifier):
@content_receiver()
def after(self, i, sibling, name=None):
"""Adds siblings after the current tag."""
self.parent._insert(sibling, idx=self._own_index + 1 + i, name=name)
return self

@content_receiver(reverse=True)
def before(self, i, sibling, name=None):
"""Adds siblings before the current tag."""
self.parent._insert(sibling, idx=self._own_index - i, name=name)
return self


class DOMNihil(SiblingsManager):
def replace_with(self, other):
"""Replace this element with the given DOMElement."""
self.after(other)
self.parent.pop(self._own_index)
return other

def remove(self):
"""Detach this element from his father."""
if self._own_index is not None and self.parent:
self.parent.pop(self._own_index)
return self

def _detach_childs(self, idx_from=None, idx_to=None):
"""Moves all the childs to a new father"""
idx_from = idx_from or 0
idx_to = idx_to or len(self.childs)
removed = self.childs[idx_from:idx_to]
for child in removed:
if issubclass(child.__class__, TempyClass):
child.parent = None
self.childs[idx_from:idx_to] = []
return removed

def move_childs(self, new_father, idx_from=None, idx_to=None):
removed = self._detach_childs(idx_from=idx_from, idx_to=idx_to)
new_father(removed)
return self

def move(self, new_father, idx=None, prepend=None, name=None):
"""Moves this element from his father to the given one."""
self.parent.pop(self._own_index)
new_father._insert(self, idx=idx, prepend=prepend, name=name)
return self

def pop(self, arg=None):
"""Removes the child at given position or by name (or name iterator).
if no argument is given removes the last."""
if arg is None:
arg = len(self.childs) - 1
if isinstance(arg, int):
try:
result = self.childs.pop(arg)
except IndexError:
raise DOMModByIndexError(self, "Given index invalid.")
if isinstance(result, TempyClass):
result.parent = None
else:
result = []
if isinstance(arg, str):
arg = [arg]
for x in arg:
try:
result.append(getattr(self, x))
except AttributeError:
raise DOMModByKeyError(self, "Given search key invalid. No child found.")
for x in result:
self.childs.remove(x)
if isinstance(x, TempyClass):
x.parent = False
return result

def empty(self):
"""Remove all this tag's childs."""
self._detach_childs()
return self


class OperatorsModifier(DOMNihil):
def __add__(self, other):
"""Addition produces a copy of the left operator, containig the right operator as a child."""
return self.clone()(other)
Expand Down Expand Up @@ -81,21 +168,7 @@ def __imul__(self, n):
return self.after(self * (n - 1))


class SiblingsManager(OperatorsModifier):
@content_receiver()
def after(self, i, sibling, name=None):
"""Adds siblings after the current tag."""
self.parent._insert(sibling, idx=self._own_index + 1 + i, name=name)
return self

@content_receiver(reverse=True)
def before(self, i, sibling, name=None):
"""Adds siblings before the current tag."""
self.parent._insert(sibling, idx=self._own_index - i, name=name)
return self


class DOMFather(SiblingsManager):
class DOMFather(OperatorsModifier):
@content_receiver(reverse=True)
def prepend(self, _, child, name=None):
"""Adds childs to this tag, starting from the first position."""
Expand Down Expand Up @@ -177,73 +250,5 @@ def wrap_inner(self, other):
return self


class DOMNihil(DOMWrapper):
def replace_with(self, other):
"""Replace this element with the given DOMElement."""
self.after(other)
self.parent.pop(self._own_index)
return other

def remove(self):
"""Detach this element from his father."""
if self._own_index is not None and self.parent:
self.parent.pop(self._own_index)
return self

def _detach_childs(self, idx_from=None, idx_to=None):
"""Moves all the childs to a new father"""
idx_from = idx_from or 0
idx_to = idx_to or len(self.childs)
removed = self.childs[idx_from:idx_to]
for child in removed:
if issubclass(child.__class__, TempyClass):
child.parent = None
self.childs[idx_from:idx_to] = []
return removed

def move_childs(self, new_father, idx_from=None, idx_to=None):
removed = self._detach_childs(idx_from=idx_from, idx_to=idx_to)
new_father(removed)
return self

def move(self, new_father, idx=None, prepend=None, name=None):
"""Moves this element from his father to the given one."""
self.parent.pop(self._own_index)
new_father._insert(self, idx=idx, prepend=prepend, name=name)
return self

def pop(self, arg=None):
"""Removes the child at given position or by name (or name iterator).
if no argument is given removes the last."""
if arg is None:
arg = len(self.childs) - 1
if isinstance(arg, int):
try:
result = self.childs.pop(arg)
except IndexError:
raise DOMModByIndexError(self, "Given index invalid.")
if isinstance(result, TempyClass):
result.parent = None
else:
result = []
if isinstance(arg, str):
arg = [arg]
for x in arg:
try:
result.append(getattr(self, x))
except AttributeError:
raise DOMModByKeyError(self, "Given search key invalid. No child found.")
for x in result:
self.childs.remove(x)
if isinstance(x, TempyClass):
x.parent = False
return result

def empty(self):
"""Remove all this tag's childs."""
self._detach_childs()
return self


class DOMModifier(DOMNihil):
class DOMModifier(DOMWrapper):
pass
2 changes: 1 addition & 1 deletion tempy/navigator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from .bases import TempyClass


class DOMNavigator:
class DOMNavigator(TempyClass):
@property
def root(self):
return self.parent.root if self.parent else self
Expand Down
64 changes: 59 additions & 5 deletions tempy/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
"""Base class for rendering"""
from html import escape
from numbers import Number
from functools import partial

from .bases import TempyClass
from .tempyrepr import TempyPlace, TempyREPR


class DOMRenderer:
class DOMRenderer(TempyClass):

def __repr__(self):
return "<%s.%s %s.%s%s%s>" % (
self.__module__,
Expand All @@ -30,11 +33,11 @@ def _iter_child_renders(self, pretty=False):
else:
yield child.render(pretty=pretty)
elif not issubclass(child.__class__, TempyClass):
tempyREPR_cls = self._search_for_view(child)
if tempyREPR_cls:
tempy_repr_cls = self._search_for_view(child)
if tempy_repr_cls:
# If there is a TempyREPR class defined in the child class we make a DOMElement out of it
# this abomination is used to avoid circular imports
class Patched(tempyREPR_cls, self.__class__):
class Patched(tempy_repr_cls, self.__class__):
def __init__(s, obj, *args, **kwargs):
# Forced adoption of the patched element as son of us
s.parent = self
Expand All @@ -46,6 +49,53 @@ def __init__(s, obj, *args, **kwargs):
else:
yield escape(str(child))

@staticmethod
def _filter_classes(cls_list, cls_type):
"""Filters a list of classes and yields TempyREPR subclasses"""
for cls in cls_list:
if isinstance(cls, type) and issubclass(cls, cls_type):
if cls_type == TempyPlace and cls._base_place:
pass
else:
yield cls

def _evaluate_tempy_repr(self, child, repr_cls):
"""Assign a score ito a TempyRepr class.
The scores depends on the current scope and position of the object in which the TempyREPR is found."""
score = 0
if repr_cls.__name__ == self.__class__.__name__:
# One point if the REPR have the same name of the container
score += 1
elif repr_cls.__name__ == self.root.__class__.__name__:
# One point if the REPR have the same name of the Tempy tree root
score += 1

# Add points defined in scorers methods of used TempyPlaces
for parent_cls in self._filter_classes(repr_cls.__mro__[1:], TempyPlace):
for scorer in (
method for method in dir(parent_cls) if method.startswith("_reprscore")
):
score += getattr(parent_cls, scorer, lambda *args: 0)(
parent_cls, self, child
)
return score

def _search_for_view(self, obj):
"""Searches for TempyREPR class declarations in the child's class.
If at least one TempyREPR is found, it uses the best one to make a Tempy object.
Otherwise the original object is returned.
"""
evaluator = partial(self._evaluate_tempy_repr, obj)
sorted_reprs = sorted(
self._filter_classes(obj.__class__.__dict__.values(), TempyREPR),
key=evaluator,
reverse=True,
)
if sorted_reprs:
# If we find some TempyREPR, we return the one with the best score.
return sorted_reprs[0]
return None

def render(self, *args, **kwargs):
"""Placeholder for subclass implementation"""
raise NotImplementedError
Expand All @@ -67,7 +117,7 @@ def render_attrs(self):
return "".join(ret)


class CodeRenderer:
class CodeRenderer(TempyClass):
def to_code(self, pretty=False):
ret = []
prettying = "\n" + ("\t" * self._depth) if pretty else ""
Expand Down Expand Up @@ -104,3 +154,7 @@ def formatter(k, v):

twist_specials = {v: k for k, v in self._SPECIAL_ATTRS.items()}
return ", ".join(formatter(k, v) for k, v in self.attrs.items() if v)


class TempyRenderer(CodeRenderer, DOMRenderer):
pass

0 comments on commit 0a8d250

Please sign in to comment.