Skip to content

Commit

Permalink
Review html module
Browse files Browse the repository at this point in the history
  • Loading branch information
gi0baro committed Oct 10, 2024
1 parent 57af3b6 commit e2b0ab0
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 12 deletions.
45 changes: 33 additions & 12 deletions emmett/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ def __bool__(self):


class HtmlTag:
__slots__ = ["name", "parent", "components", "attributes"]

rules = {
"ul": ["li"],
"ol": ["li"],
Expand Down Expand Up @@ -72,6 +74,9 @@ def wrap(component, rules):
def __call__(self, *components, **attributes):
rules = self.rules.get(self.name, [])
self.components = [self.wrap(comp, rules) for comp in components]
# legacy "data" attribute
if _data := attributes.pop("data", None):
attributes["_data"] = _data
self.attributes = attributes
for component in self.components:
if isinstance(component, HtmlTag):
Expand Down Expand Up @@ -154,20 +159,32 @@ def find(self, expr):
tags.add(self)
return tags

@staticmethod
def _build_html_attributes_items(attrs, namespace=None):
if namespace:
for k, v in sorted(attrs.items()):
nk = f"{namespace}-{k}"
if v is True:
yield (nk, k)
else:
yield (nk, htmlescape(v))
else:
for k, v in filter(lambda item: item[0].startswith("_") and item[1] is not None, sorted(attrs.items())):
nk = k[1:]
if isinstance(v, dict):
for item in HtmlTag._build_html_attributes_items(v, nk):
yield item
elif v is True:
yield (nk, nk)
else:
yield (nk, htmlescape(v))

def _build_html_attributes(self):
return " ".join(
'%s="%s"' % (k[1:], k[1:] if v is True else htmlescape(v))
for (k, v) in sorted(self.attributes.items())
if k.startswith("_") and v is not None
)
return " ".join(f'{k}="{v}"' for k, v in self._build_html_attributes_items(self.attributes))

def __html__(self):
name = self.name
attrs = self._build_html_attributes()
data = self.attributes.get("data", {})
data_attrs = " ".join('data-%s="%s"' % (k, htmlescape(v)) for k, v in data.items())
if data_attrs:
attrs = attrs + " " + data_attrs
attrs = " " + attrs if attrs else ""
if name in self._self_closed:
return "<%s%s />" % (name, attrs)
Expand All @@ -187,6 +204,8 @@ def __getitem__(self, name):


class cat(HtmlTag):
__slots__ = []

def __init__(self, *components):
self.components = list(components)
self.attributes = {}
Expand All @@ -196,11 +215,13 @@ def __html__(self):


class asis(HtmlTag):
def __init__(self, text):
self.text = text
__slots__ = []

def __init__(self, val):
self.name = val

def __html__(self):
return _to_str(self.text)
return _to_str(self.name)


def _to_str(obj):
Expand Down
42 changes: 42 additions & 0 deletions tests/test_html.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from emmett.html import asis, cat, tag


def test_tag_self_closed():
br = tag.br()
assert str(br) == "<br />"


def test_tag_non_closed():
p = tag.p()
assert str(p) == "<p></p>"


def test_tag_components():
t = tag.div(tag.p(), tag.p())
assert str(t) == "<div><p></p><p></p></div>"


def test_tag_attributes():
d = tag.div(_class="test", _id="test", _test="test")
assert str(d) == '<div class="test" id="test" test="test"></div>'


def test_tag_attributes_dict():
d = tag.div(_class="test", _hx={"foo": "bar"})
assert str(d) == '<div class="test" hx-foo="bar"></div>'


def test_tag_attributes_data():
d1 = tag.div(data={"foo": "bar"})
d2 = tag.div(_data={"foo": "bar"})
assert str(d1) == str(d2) == '<div data-foo="bar"></div>'


def test_cat():
t = cat(tag.p(), tag.p())
assert str(t) == "<p></p><p></p>"


def test_asis():
t = asis('{foo: "bar"}')
assert str(t) == '{foo: "bar"}'

0 comments on commit e2b0ab0

Please sign in to comment.