11from __future__ import annotations
22
3- import re
43from dataclasses import dataclass
54from pathlib import Path
65from typing import Any , Callable , Iterator , Sequence
98from idom import component , create_context , use_context , use_memo , use_state
109from idom .core .types import VdomAttributesAndChildren , VdomDict
1110from idom .core .vdom import coalesce_attributes_and_children
12- from idom .types import BackendImplementation , ComponentType , Context , Location
11+ from idom .types import ComponentType , Context , Location
1312from idom .web .module import export , module_from_file
14- from starlette .routing import compile_path
13+ from starlette .routing import compile_path as _compile_starlette_path
14+
15+ from idom_router .types import RoutePattern , RouteCompiler , Route
1516
1617try :
1718 from typing import Protocol
1819except ImportError : # pragma: no cover
1920 from typing_extensions import Protocol # type: ignore
2021
2122
22- class RouterConstructor (Protocol ):
23- def __call__ (self , * routes : Route ) -> ComponentType :
24- ...
25-
26-
27- def create_router (
28- implementation : BackendImplementation [Any ] | Callable [[], Location ]
29- ) -> RouterConstructor :
30- if isinstance (implementation , BackendImplementation ):
31- use_location = implementation .use_location
32- elif callable (implementation ):
33- use_location = implementation
34- else :
35- raise TypeError (
36- "Expected a 'BackendImplementation' or "
37- f"'use_location' hook, not { implementation } "
38- )
39-
40- @component
41- def router (* routes : Route ) -> ComponentType | None :
42- initial_location = use_location ()
43- location , set_location = use_state (initial_location )
44- compiled_routes = use_memo (
45- lambda : _iter_compile_routes (routes ), dependencies = routes
46- )
47- for r in compiled_routes :
48- match = r .pattern .match (location .pathname )
49- if match :
50- return _LocationStateContext (
51- r .element ,
52- value = _LocationState (
53- location ,
54- set_location ,
55- {k : r .converters [k ](v ) for k , v in match .groupdict ().items ()},
56- ),
57- key = r .pattern .pattern ,
58- )
59- return None
60-
61- return router
62-
23+ def compile_starlette_route (path : str ) -> RoutePattern :
24+ pattern , _ , converters = _compile_starlette_path (path )
25+ return RoutePattern (pattern , {k : v .convert for k , v in converters .items ()})
6326
64- @dataclass
65- class Route :
66- path : str
67- element : Any
68- routes : Sequence [Route ]
6927
70- def __init__ (self , path : str , element : Any | None , * routes : Route ) -> None :
71- self .path = path
72- self .element = element
73- self .routes = routes
28+ @component
29+ def router (
30+ * routes : Route ,
31+ compiler : RouteCompiler = compile_starlette_route ,
32+ ) -> ComponentType | None :
33+ initial_location = use_location ()
34+ location , set_location = use_state (initial_location )
35+ compiled_routes = use_memo (
36+ lambda : [(compiler (path ), element ) for path , element in _iter_routes (routes )],
37+ dependencies = routes ,
38+ )
39+ for compiled_route , element in compiled_routes :
40+ match = compiled_route .pattern .match (location .pathname )
41+ if match :
42+ return _LocationStateContext (
43+ element ,
44+ value = _LocationState (
45+ location ,
46+ set_location ,
47+ {
48+ k : compiled_route .converters [k ](v )
49+ for k , v in match .groupdict ().items ()
50+ },
51+ ),
52+ key = compiled_route .pattern .pattern ,
53+ )
54+ return None
7455
7556
7657@component
@@ -113,28 +94,13 @@ def use_query(
11394 )
11495
11596
116- def _iter_compile_routes (routes : Sequence [Route ]) -> Iterator [_CompiledRoute ]:
117- for path , element in _iter_routes (routes ):
118- pattern , _ , converters = compile_path (path )
119- yield _CompiledRoute (
120- pattern , {k : v .convert for k , v in converters .items ()}, element
121- )
122-
123-
12497def _iter_routes (routes : Sequence [Route ]) -> Iterator [tuple [str , Any ]]:
12598 for r in routes :
12699 for path , element in _iter_routes (r .routes ):
127100 yield r .path + path , element
128101 yield r .path , r .element
129102
130103
131- @dataclass
132- class _CompiledRoute :
133- pattern : re .Pattern [str ]
134- converters : dict [str , Callable [[Any ], Any ]]
135- element : Any
136-
137-
138104def _use_location_state () -> _LocationState :
139105 location_state = use_context (_LocationStateContext )
140106 assert location_state is not None , "No location state. Did you use a Router?"
@@ -151,10 +117,6 @@ class _LocationState:
151117_LocationStateContext : Context [_LocationState | None ] = create_context (None )
152118
153119_Link = export (
154- module_from_file (
155- "idom-router" ,
156- file = Path (__file__ ).parent / "bundle.js" ,
157- fallback = "⏳" ,
158- ),
120+ module_from_file ("idom-router" , file = Path (__file__ ).parent / "bundle.js" ),
159121 "Link" ,
160122)
0 commit comments