Skip to content

Commit 8077994

Browse files
authored
initial work on router compiler (#6)
* initial work on router compiler * fix tests and upgrade to compat latest idom ver * rework route compiler interface This allows compilers to work in a wider variety of ways. * rework based on feedback * remove starlette as dep * remove print
1 parent d014434 commit 8077994

File tree

10 files changed

+352
-225
lines changed

10 files changed

+352
-225
lines changed

idom_router/__init__.py

+11-13
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
11
# the version is statically loaded by setup.py
22
__version__ = "0.0.1"
33

4-
from .router import (
5-
Route,
6-
RouterConstructor,
7-
create_router,
8-
link,
9-
use_location,
10-
use_params,
11-
use_query,
12-
)
4+
from . import simple
5+
from .core import create_router, link, route, router_component, use_params, use_query
6+
from .types import Route, RouteCompiler, RouteResolver
137

14-
__all__ = [
8+
__all__ = (
159
"create_router",
1610
"link",
11+
"route",
12+
"route",
1713
"Route",
18-
"RouterConstructor",
19-
"use_location",
14+
"RouteCompiler",
15+
"router_component",
16+
"RouteResolver",
17+
"simple",
2018
"use_params",
2119
"use_query",
22-
]
20+
)

idom_router/core.py

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass, replace
4+
from pathlib import Path
5+
from typing import Any, Callable, Iterator, Sequence, TypeVar
6+
from urllib.parse import parse_qs
7+
8+
from idom import (
9+
component,
10+
create_context,
11+
use_context,
12+
use_location,
13+
use_memo,
14+
use_state,
15+
)
16+
from idom.backend.hooks import ConnectionContext, use_connection
17+
from idom.backend.types import Connection, Location
18+
from idom.core.types import VdomChild, VdomDict
19+
from idom.types import ComponentType, Context, Location
20+
from idom.web.module import export, module_from_file
21+
22+
from idom_router.types import Route, RouteCompiler, Router, RouteResolver
23+
24+
R = TypeVar("R", bound=Route)
25+
26+
27+
def route(path: str, element: Any | None, *routes: Route) -> Route:
28+
return Route(path, element, routes)
29+
30+
31+
def create_router(compiler: RouteCompiler[R]) -> Router[R]:
32+
"""A decorator that turns a route compiler into a router"""
33+
34+
def wrapper(*routes: R) -> ComponentType:
35+
return router_component(*routes, compiler=compiler)
36+
37+
return wrapper
38+
39+
40+
@component
41+
def router_component(
42+
*routes: R,
43+
compiler: RouteCompiler[R],
44+
) -> ComponentType | None:
45+
old_conn = use_connection()
46+
location, set_location = use_state(old_conn.location)
47+
48+
resolvers = use_memo(
49+
lambda: tuple(map(compiler, _iter_routes(routes))),
50+
dependencies=(compiler, hash(routes)),
51+
)
52+
53+
match = use_memo(lambda: _match_route(resolvers, location))
54+
55+
if match is not None:
56+
element, params = match
57+
return ConnectionContext(
58+
_route_state_context(element, value=_RouteState(set_location, params)),
59+
value=Connection(old_conn.scope, location, old_conn.carrier),
60+
)
61+
62+
return None
63+
64+
65+
@component
66+
def link(*children: VdomChild, to: str, **attributes: Any) -> VdomDict:
67+
set_location = _use_route_state().set_location
68+
attrs = {
69+
**attributes,
70+
"to": to,
71+
"onClick": lambda event: set_location(Location(**event)),
72+
}
73+
return _link(attrs, *children)
74+
75+
76+
def use_params() -> dict[str, Any]:
77+
"""Get parameters from the currently matching route pattern"""
78+
return _use_route_state().params
79+
80+
81+
def use_query(
82+
keep_blank_values: bool = False,
83+
strict_parsing: bool = False,
84+
errors: str = "replace",
85+
max_num_fields: int | None = None,
86+
separator: str = "&",
87+
) -> dict[str, list[str]]:
88+
"""See :func:`urllib.parse.parse_qs` for parameter info."""
89+
return parse_qs(
90+
use_location().search[1:],
91+
keep_blank_values=keep_blank_values,
92+
strict_parsing=strict_parsing,
93+
errors=errors,
94+
max_num_fields=max_num_fields,
95+
separator=separator,
96+
)
97+
98+
99+
def _iter_routes(routes: Sequence[R]) -> Iterator[R]:
100+
for parent in routes:
101+
for child in _iter_routes(parent.routes):
102+
yield replace(child, path=parent.path + child.path) # type: ignore[misc]
103+
yield parent
104+
105+
106+
def _match_route(
107+
compiled_routes: Sequence[RouteResolver], location: Location
108+
) -> tuple[Any, dict[str, Any]] | None:
109+
for resolver in compiled_routes:
110+
match = resolver.resolve(location.pathname)
111+
if match is not None:
112+
return match
113+
return None
114+
115+
116+
_link = export(
117+
module_from_file("idom-router", file=Path(__file__).parent / "bundle.js"),
118+
"Link",
119+
)
120+
121+
122+
@dataclass
123+
class _RouteState:
124+
set_location: Callable[[Location], None]
125+
params: dict[str, Any]
126+
127+
128+
def _use_route_state() -> _RouteState:
129+
route_state = use_context(_route_state_context)
130+
assert route_state is not None
131+
return route_state
132+
133+
134+
_route_state_context: Context[_RouteState | None] = create_context(None)

idom_router/router.py

-160
This file was deleted.

0 commit comments

Comments
 (0)