Hrabal/TemPy

View on GitHub
tempy/tempy.py

Summary

Maintainability
A
2 hrs
Test Coverage
# -*- coding: utf-8 -*-
# @author: Federico Cerchiari <federicocerchiari@gmail.com>
"""Main Tempy classes"""
from copy import copy

from .modifier import DOMModifier
from .renderer import TempyRenderer
from .navigator import DOMNavigator
from .exceptions import WrongContentError


class DOMElement(TempyRenderer, DOMNavigator, DOMModifier):
    """Takes care of the tree structure using the "childs" and "parent" attributes.
    Manages the DOM manipulation with proper valorization of those two.
    """

    _from_factory = False

    def __init__(self, **kwargs):
        self._name = None
        self.childs = []
        self.parent = None
        self.content_data = kwargs
        for cls in reversed(self.__class__.__mro__[:-6]):
            init = getattr(cls, "init", None)
            if init and init.__name__ in cls.__dict__:
                init(self)

    def __hash__(self):
        return id(self)

    def __bool__(self):
        # Is it normal that without explicit __bool__ definition
        # custom classes evaluates to False?
        return True

    def __eq__(self, other):
        if self.__class__ != other.__class__:
            return False
        comp_dicts = [
            {
                "name": t._name,
                "childs": [id(c) for c in t.childs if isinstance(c, DOMElement)],
                "content_data": t.content_data,
            }
            for t in (self, other)
        ]
        return comp_dicts[0] == comp_dicts[1]

    def __getitem__(self, i):
        return self.childs[i]

    def __iter__(self):
        return iter(self.childs)

    def __next__(self):
        return next(iter(self.childs))

    def __reversed__(self):
        return iter(self.childs[::-1])

    def __contains__(self, x):
        return x in self.childs

    def __len__(self):
        return len(self.childs)

    def __copy__(self):
        return self.__class__()(
            copy(c) if isinstance(c, DOMElement) else c for c in self.childs
        )

    @property
    def _depth(self):
        return 0 if self.is_root else self.parent._depth + 1

    @property
    def is_root(self):
        return self.root == self

    @property
    def _own_index(self):
        if self.parent:
            try:
                return [id(t) for t in self.parent.childs].index(id(self))
            except ValueError:
                return -1
        return -1

    @property
    def index(self):
        """Returns the position of this element in the parent's childs list.
        If the element have no parent, returns None.
        """
        return self._own_index

    @property
    def length(self):
        """Returns the number of childs."""
        return len(self.childs)

    @property
    def is_empty(self):
        """True if no childs"""
        return self.length == 0

    def data(self, key=None, **kwargs):
        """Adds or retrieve extra data to this element, this data will not be rendered.
        Every tag have a _data attribute (dict), if key is given _data[key] is returned.
        Kwargs are used to udpate this Tag's _data."""
        self.content_data.update(kwargs)
        if key:
            return self.content_data[key]
        if not kwargs:
            return self.content_data
        return self

    def inject(self, contents=None, **kwargs):
        """
        Adds content data in this element. This will be used in the rendering of this element's childs.
        Multiple injections on the same key will override the content (dict.update behavior).
        """
        if contents and not isinstance(contents, dict):
            raise WrongContentError(self, contents, "contents should be a dict")
        if not contents:
            contents = {}
        if kwargs:
            contents.update(kwargs)
        self.content_data.update(contents)
        return self

    @classmethod
    def join(cls, list_ele):
        n = len(list_ele)
        for index in range(1, 2*n-2, 2):
            list_ele.insert(index, cls())
        return list_ele

    @classmethod
    def map(cls, list_ele):
        mapped_list = [cls()(ele) for ele in list_ele]
        return mapped_list


class Escaped(DOMElement):
    def __init__(self, content, **kwargs):
        super().__init__(**kwargs)
        self.render = content