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