Last active
May 9, 2022 03:14
-
-
Save gvanrossum/a465d31d9402bae2c79e89b2f344c10c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Don't name this file html.py! | |
from __future__ import annotations | |
from typing import * | |
from dataclasses import dataclass | |
from html import escape | |
from html.parser import HTMLParser | |
Thunk = tuple[ | |
Callable[[], Any], | |
str, | |
str | None, | |
str | None, | |
] | |
AttrsDict = dict[str, str] | |
BodyList = list["str | HTMLNode"] | |
@dataclass | |
class HTMLNode: | |
tag: str|None | |
attrs: AttrsDict | |
body: BodyList | |
def __init__( | |
self, | |
tag: str|None = None, | |
attrs: AttrsDict|None = None, | |
body: BodyList |None = None, | |
): | |
self.tag = tag | |
self.attrs = {} | |
if attrs: | |
self.attrs.update(attrs) | |
self.body = [] | |
if body: | |
self.body.extend(body) | |
def __str__(self): | |
attrlist = [] | |
for key, value in self.attrs.items(): | |
attrlist.append(f' {key}="{escape(str(value))}"') | |
bodylist = [] | |
for item in self.body: | |
if isinstance(item, str): | |
item = escape(item, quote=False) | |
else: | |
item = str(item) | |
bodylist.append(item) | |
stuff = "".join(bodylist) | |
if self.tag: | |
stuff = f"<{self.tag}{''.join(attrlist)}>{stuff}</{self.tag}>" | |
return stuff | |
class HTMLBuilder(HTMLParser): | |
def __init__(self): | |
self.stack = [HTMLNode()] | |
super().__init__() | |
def handle_starttag(self, tag, attrs): | |
node = HTMLNode(tag, attrs) | |
self.stack[-1].body.append(node) | |
self.stack.append(node) | |
def handle_endtag(self, tag): | |
if tag != self.stack[-1].tag: | |
raise RuntimeError(f"unexpected </{tag}>") | |
self.stack.pop() | |
def handle_data(self, data: str): | |
self.stack[-1].body.append(data) | |
# This is the actual 'tag' function: html"<body>blah</body>"" | |
def html(*args: str | Thunk) -> HTMLTree: | |
builder = HTMLBuilder() | |
for arg in args: | |
if isinstance(arg, str): | |
builder.feed(arg) # TODO: interpret \n, \x, \u, etc. | |
else: | |
getvalue, raw, conv, spec = arg | |
value = getvalue() | |
match conv: | |
case 'r': value = repr(value) | |
case 's': value = str(value) | |
case 'a': value = ascii(value) | |
case None: pass | |
case _: raise ValueError(f"Bad conversion: {conv!r}") | |
if spec is not None: | |
value = format(value, spec) | |
if isinstance(value, HTMLNode): | |
builder.feed(str(value)) | |
elif isinstance(value, list): | |
for item in value: | |
if isinstance(item, HTMLNode): | |
builder.feed(str(item)) | |
else: | |
builder.feed(escape(str(item))) | |
else: | |
builder.feed(escape(str(value))) | |
root = builder.stack[0] | |
if not root.tag and not root.attrs: | |
stuff = root.body[:] | |
while stuff and isinstance(stuff[0], str) and stuff[0].isspace(): | |
del stuff[0] | |
while stuff and isinstance(stuff[-1], str) and stuff[-1].isspace(): | |
del stuff[-1] | |
if len(stuff) == 1: | |
return stuff[0] | |
return stuff | |
return root | |
x = HTMLNode("foo", {"x": 1, "y": "2"}, ["hohoho"]) | |
a = html""" | |
<html> | |
<body> | |
foo | |
{x} | |
bar | |
{x!s} | |
baz | |
{x!r} | |
</body> | |
</html> | |
""" | |
print(a) | |
print() | |
s = '"' | |
b = html""" | |
<html> | |
<body attr=blah" yo={1}> | |
{[html"<div class=c{i}>haha{i}</div> " for i in range(3)]} | |
</body> | |
</html> | |
""" | |
print(b) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<body> | |
foo | |
<foo x="1" y="2">hohoho</foo> | |
bar | |
<foo x="1" y="2">hohoho</foo> | |
baz | |
HTMLNode(tag='foo', attrs={'x': 1, 'y': '2'}, body=['hohoho']) | |
</body> | |
</html> | |
<html> | |
<body attr="blah"" yo="1"> | |
<div class="c0">haha0</div><div class="c1">haha1</div><div class="c2">haha2</div> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note that an interpolation may now be a list of
HTMLNode
s that are rendered as such.