Skip to content

Commit 35d3c97

Browse files
committed
Added the demoed uhtml from Python test page
1 parent 34f72d3 commit 35d3c97

File tree

6 files changed

+152
-0
lines changed

6 files changed

+152
-0
lines changed

test/uhtml/config.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[[fetch]]
2+
files = ["./js_template.py", "./uhtml.py"]

test/uhtml/index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width,initial-scale=1.0">
6+
<title>Template</title>
7+
<script defer src="https://cdn.jsdelivr.net/npm/uhtml/es.js"></script>
8+
<script type="module" src="https://cdn.jsdelivr.net/npm/polyscript"></script>
9+
<script type="pyodide" src="./index.py" config="./config.toml"></script>
10+
</head>
11+
<body></body>
12+
</html>

test/uhtml/index.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from js import document
2+
from uhtml import render, html
3+
4+
def update(count):
5+
render(document.body, html(
6+
"""
7+
<h3 onclick=${h3_click}>
8+
Hello ${value}
9+
</h3>
10+
<button onclick=${button_click}>
11+
Clicks ${count}
12+
</button>
13+
""",
14+
h3_click=lambda event: print(event.type),
15+
value="World",
16+
button_click=lambda _: update(count + 1),
17+
count=count
18+
))
19+
20+
update(0)

test/uhtml/js_template.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# A silly idea by Andrea Giammarchi
2+
from string import Template as _Template
3+
4+
# The goal of this utility is to create a JS
5+
# Template Literal Tag like function that accepts
6+
# an immutable template as tuple and zero or more ordered
7+
# interpolated values per each gap between chunks.
8+
# If a cache dictionary is passed, it never parses the same
9+
# template string more than once, improving performance
10+
# for more complex scenarios / use cases.
11+
def tag(fn, cache=None):
12+
return lambda tpl, **kw: _tag(tpl, cache)(fn, **kw)
13+
14+
def _create(tpl):
15+
i = 0
16+
d = {}
17+
u = chr(0)
18+
template = _Template(tpl)
19+
identifiers = template.get_identifiers()
20+
# map all identifiers as chunks that
21+
# can be split without losing details
22+
for k in identifiers:
23+
d[k] = f"{u}{i}."
24+
i += 1
25+
s = template.substitute(d)
26+
a = s.split(u)
27+
i = 1
28+
keys = []
29+
# create a template after removing chunks
30+
# leftovers, placing back ordered expected fields
31+
for k in a[1:]:
32+
d = k.index(".")
33+
x = k[0:d]
34+
keys.append(identifiers[int(x)])
35+
a[i] = k[(d+1):]
36+
i += 1
37+
# make the template immutable
38+
t = tuple(a)
39+
return lambda fn, **kw: fn(t, *[kw[k] for k in keys])
40+
41+
# given a template string, maps all non interpolated
42+
# parts as tuple and orchestrate ordered values to send
43+
def _tag(tpl, tags):
44+
if tags == None: return _create(tpl)
45+
if not tpl in tags: tags[tpl] = _create(tpl)
46+
return tags[tpl]

test/uhtml/js_template.test.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from js_template import tag
2+
3+
# known = []
4+
5+
def _flatten(tpl, *values):
6+
# if not tpl in known:
7+
# known.append(tpl)
8+
# print("new template:", "".join(tpl))
9+
c = []
10+
for i in range(len(tpl)):
11+
if i: c.append(str(values[i - 1]))
12+
c.append(tpl[i])
13+
return "".join(c)
14+
15+
# A really not interesting use case such as
16+
# a generic tag that simply flattens the string
17+
plain = tag(_flatten, cache={})
18+
19+
print(plain("this is ${a} ${b} ${a}!", a="one", b="o"))
20+
print(plain("this is ${a} ${b} ${a}!", a="two", b="o"))
21+
print(plain("this is ${a} ${b} ${a}!", a="three", b="o"))
22+
23+
24+
print(
25+
plain(
26+
"${a}${b}${c}${d}${e}${f}${g}${h}${i}${j}${k}${a}${b}",
27+
a="a",
28+
b="b",
29+
c="c",
30+
d="d",
31+
e="e",
32+
f="f",
33+
g="g",
34+
h="h",
35+
i="i",
36+
j="j",
37+
k="k"
38+
)
39+
)

test/uhtml/uhtml.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from js import uhtml as _uhtml
2+
from js_template import tag as _tag
3+
from pyodide.ffi import to_js as _to_js
4+
5+
_tuples = []
6+
_arrays = []
7+
_cache = {}
8+
9+
# TODO: if values is a callback the to_js creates a proxy of it each time!
10+
# Find a smart way to re-map *once* callbacks at specific indexes
11+
# so that we can destroy previous references when needed.
12+
# Please keep in mind the list of same template but different
13+
# interpolations values per invoke so that we should also
14+
# not destroy other elements created via other passed values.
15+
16+
def _kind(uhtml):
17+
# hole convention from uhtml, don't @ me :D
18+
def hole(tpl, *values):
19+
# this ensures that the passed "template" is
20+
# always the same reference in the JS side
21+
if not tpl in _tuples:
22+
_tuples.append(tpl)
23+
_arrays.append(_to_js(tpl))
24+
i = _tuples.index(tpl)
25+
# I've no idea why this lambda is needed but it works without
26+
# needing to create_proxy or other things I don't fully understand
27+
return lambda: uhtml(_arrays[i], *[_to_js(v) for v in values])
28+
return hole
29+
30+
# export uhtml API as Python
31+
html = _tag(_kind(_uhtml.html), cache=_cache)
32+
svg = _tag(_kind(_uhtml.svg), cache=_cache)
33+
render = _uhtml.render

0 commit comments

Comments
 (0)