Skip to content

Commit ff96eab

Browse files
committed
WIP
1 parent 90a2053 commit ff96eab

File tree

8 files changed

+79
-80
lines changed

8 files changed

+79
-80
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
<a href="https://travis-ci.com/rmorshea/idom">
44
<img alt="Build Status" src="https://travis-ci.com/rmorshea/idom.svg?branch=master"/>
55
</a>
6-
<a href="https://pypi.python.org/pypi/idom">
7-
<img alt="Version Info" src="https://img.shields.io/pypi/v/spectate.svg"/>
8-
</a>
96
<a href="https://github.com/rmorshea/idom/blob/master/LICENSE"/>
107
<img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-purple.svg">
118
</a>
9+
<a href="https://pypi.python.org/pypi/idom">
10+
<img alt="Version Info" src="https://img.shields.io/pypi/v/spectate.svg"/>
11+
</a>
1212

1313
Libraries for creating and controlling interactive web pages with Python 3.6 and above.
1414

examples/introduction.ipynb

+1-1
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@
621621
"name": "python",
622622
"nbconvert_exporter": "python",
623623
"pygments_lexer": "ipython3",
624-
"version": "3.7.3"
624+
"version": "3.6.8"
625625
}
626626
},
627627
"nbformat": 4,

requirements/docs.txt

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# copied from prod.txt
22
sanic >=18.12, <19.0
3-
typing-extensions >=3.7.2, <4.0
43

54
# copied from dev.txt
65
pytest

requirements/prod.txt

-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
sanic >=18.12, <19.0
2-
typing-extensions >=3.7.2, <4.0

src/py/idom/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "0.4.1"
1+
__version__ = "0.4.2"
22

33
from .core import element, Element, Events, Layout
44
from .widgets import node, Image, hotswap, display, html, Input

src/py/idom/core/element.py

+12-17
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,15 @@
44
from functools import wraps
55
import time
66

7-
from typing_extensions import Protocol
8-
from typing import Dict, Callable, Any, List, Optional, overload, Awaitable, Mapping
7+
from typing import Dict, Callable, Any, List, Optional, overload, Awaitable
98

109
import idom
1110

1211
from .utils import bound_id
1312

1413

1514
ElementConstructor = Callable[..., "Element"] # Element constructor
16-
17-
18-
class _EF(Protocol):
19-
"""Element function."""
20-
21-
def __call__(
22-
self, element: "Element", *args: Any, **kwargs: Any
23-
) -> Awaitable[Dict[str, Any]]:
24-
...
15+
ElementRenderFunction = Callable[..., Awaitable[Any]]
2516

2617

2718
@overload
@@ -30,20 +21,22 @@ def element(function: Callable[..., Any]) -> ElementConstructor:
3021

3122

3223
@overload
33-
def element(*, state: Optional[str] = None) -> Callable[[_EF], ElementConstructor]:
24+
def element(
25+
*, state: Optional[str] = None
26+
) -> Callable[[ElementRenderFunction], ElementConstructor]:
3427
...
3528

3629

3730
def element(
38-
function: Optional[_EF] = None, state: Optional[str] = None
31+
function: Optional[ElementRenderFunction] = None, state: Optional[str] = None
3932
) -> Callable[..., Any]:
4033
"""A decorator for defining an :class:`Element`.
4134
4235
Parameters:
4336
function: The function that will render a :term:`VDOM` model.
4437
"""
4538

46-
def setup(func: _EF) -> ElementConstructor:
39+
def setup(func: ElementRenderFunction) -> ElementConstructor:
4740
@wraps(func)
4841
def constructor(*args: Any, **kwargs: Any) -> Element:
4942
element = Element(func, state)
@@ -75,7 +68,7 @@ def id(self) -> str:
7568
return self._element_id
7669

7770
@abc.abstractmethod
78-
async def render(self) -> Mapping[str, Any]:
71+
async def render(self) -> Any:
7972
...
8073

8174
def mount(self, layout: "idom.Layout") -> None:
@@ -134,7 +127,9 @@ class Element(AbstractElement):
134127
"_stop_animation",
135128
)
136129

137-
def __init__(self, function: _EF, state_parameters: Optional[str]):
130+
def __init__(
131+
self, function: ElementRenderFunction, state_parameters: Optional[str]
132+
):
138133
super().__init__()
139134
self._function = function
140135
self._function_signature = inspect.signature(function)
@@ -205,7 +200,7 @@ async def wrapper() -> None:
205200
else:
206201
return setup(function)
207202

208-
async def render(self) -> Mapping[str, Any]:
203+
async def render(self) -> Any:
209204
"""Render the element's :term:`VDOM` model."""
210205
# load update and reset for next render
211206
state = self._state

src/py/idom/tools.py

+35-36
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from html.parser import HTMLParser as _HTMLParser
22

3-
from typing import List, Tuple, Any, Dict, Union, Optional, TypeVar, Generic, Callable
3+
from typing import List, Tuple, Any, Dict, TypeVar, Generic, Callable
44

55

66
_R = TypeVar("_R", bound=Any) # Var reference
@@ -41,18 +41,18 @@ def handle():
4141
return idom.node("button", "Use" eventHandlers=events)
4242
"""
4343

44-
__slots__ = ("__current",)
44+
__slots__ = ("_current",)
4545

4646
def __init__(self, value: _R) -> None:
47-
self.__current = value
47+
self._current = value
4848

4949
def set(self, new: _R) -> _R:
50-
old = self.__current
51-
self.__current = new
50+
old = self._current
51+
self._current = new
5252
return old
5353

5454
def get(self) -> _R:
55-
return self.__current
55+
return self._current
5656

5757
def __eq__(self, other: Any) -> bool:
5858
if isinstance(other, Var):
@@ -64,38 +64,46 @@ def __repr__(self) -> str:
6464
return "Var(%r)" % self.get()
6565

6666

67-
_ModelOrStr = Union[Dict[str, Any], str]
68-
_ModelTransform = Callable[[_ModelOrStr], None]
67+
_ModelTransform = Callable[[Dict[str, Any]], Any]
6968

7069

71-
def html_to_vdom(
72-
source: str, transform: Optional[_ModelTransform] = None
73-
) -> List[Dict[str, Any]]:
70+
def html_to_vdom(source: str, *transforms: _ModelTransform) -> Dict[str, Any]:
7471
"""Transform HTML into a DOM model
7572
7673
Parameters:
7774
source:
7875
The raw HTML as a string
79-
transform:
80-
A function for transforming each model as it is created. For example,
81-
you might use a transform function to add highlighting to a ``<code/>``
82-
block.
76+
transforms:
77+
Functions of the form ``transform(old) -> new`` where ``old`` is a VDOM
78+
dictionary which will be replaced by ``new``. You might use a transform
79+
function to add highlighting to a ``<code/>`` block.
8380
"""
84-
parser = HtmlParser(transform)
81+
parser = HtmlParser()
8582
parser.feed(source)
86-
return parser.model()
83+
root = parser.model()
84+
to_visit = [root]
85+
while to_visit:
86+
node = to_visit.pop(0)
87+
if isinstance(node, dict) and "children" in node:
88+
transformed = []
89+
for child in node["children"]:
90+
if isinstance(child, dict):
91+
for t in transforms:
92+
child = t(child)
93+
if child is not None:
94+
transformed.append(child)
95+
to_visit.append(child)
96+
node["children"] = transformed
97+
if "attributes" in node and not node["attributes"]:
98+
del node["attributes"]
99+
if "children" in node and not node["children"]:
100+
del node["children"]
101+
return root
87102

88103

89104
class HtmlParser(_HTMLParser):
90-
def __init__(self, transform: Optional[_ModelTransform] = None):
91-
super().__init__()
92-
if transform is not None:
93-
self._transform = transform
94-
95-
def model(self) -> List[Dict[str, Any]]:
96-
root: Dict[str, Any] = self._node_stack[0]
97-
root_children: List[Dict[str, Any]] = root["children"]
98-
return root_children
105+
def model(self) -> Dict[str, Any]:
106+
return self._node_stack[0]
99107

100108
def feed(self, data: str) -> None:
101109
self._node_stack.append(self._make_node("div", {}))
@@ -112,12 +120,7 @@ def handle_starttag(self, tag: str, attrs: List[Tuple[str, str]]) -> None:
112120
self._node_stack.append(new)
113121

114122
def handle_endtag(self, tag: str) -> None:
115-
node = self._node_stack.pop(-1)
116-
self._transform(node)
117-
if not node["attributes"]:
118-
del node["attributes"]
119-
if not node["children"]:
120-
del node["children"]
123+
del self._node_stack[-1]
121124

122125
def handle_data(self, data: str) -> None:
123126
self._node_stack[-1]["children"].append(data)
@@ -134,7 +137,3 @@ def _make_node(tag: str, attrs: Dict[str, Any]) -> Dict[str, Any]:
134137
style_dict[camel_case_key] = v
135138
attrs["style"] = style_dict
136139
return {"tagName": tag, "attributes": attrs, "children": []}
137-
138-
@staticmethod
139-
def _transform(node: Dict[str, Any]) -> None:
140-
...

src/py/tests/test_tools.py renamed to src/py/tests/test_idom/test_tools.py

+27-20
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ def test_var_get():
5050
],
5151
)
5252
def test_html_to_vdom(case):
53-
assert html_to_vdom(case["source"]) == [case["model"]]
53+
assert html_to_vdom(case["source"]) == {
54+
"tagName": "div",
55+
"children": [case["model"]],
56+
}
5457

5558

5659
def test_html_to_vdom_transform():
@@ -59,23 +62,27 @@ def test_html_to_vdom_transform():
5962
def make_links_blue(node):
6063
if node["tagName"] == "a":
6164
node["attributes"]["style"] = {"color": "blue"}
65+
return node
6266

63-
assert html_to_vdom(source, make_links_blue) == [
64-
{
65-
"tagName": "p",
66-
"children": [
67-
"hello ",
68-
{
69-
"tagName": "a",
70-
"children": ["world"],
71-
"attributes": {"style": {"color": "blue"}},
72-
},
73-
" and ",
74-
{
75-
"tagName": "a",
76-
"children": ["universe"],
77-
"attributes": {"style": {"color": "blue"}},
78-
},
79-
],
80-
}
81-
]
67+
expected = {
68+
"tagName": "p",
69+
"children": [
70+
"hello ",
71+
{
72+
"tagName": "a",
73+
"children": ["world"],
74+
"attributes": {"style": {"color": "blue"}},
75+
},
76+
" and ",
77+
{
78+
"tagName": "a",
79+
"children": ["universe"],
80+
"attributes": {"style": {"color": "blue"}},
81+
},
82+
],
83+
}
84+
85+
assert html_to_vdom(source, make_links_blue) == {
86+
"tagName": "div",
87+
"children": [expected],
88+
}

0 commit comments

Comments
 (0)