|
2 | 2 |
|
3 | 3 | from __future__ import annotations |
4 | 4 |
|
5 | | -import re |
6 | | -import uuid |
7 | | -from typing import Any, Callable |
8 | | - |
9 | | -from typing_extensions import TypeAlias, TypedDict |
10 | | - |
11 | 5 | from reactpy_router.core import create_router |
12 | | -from reactpy_router.types import Route |
| 6 | +from reactpy_router.resolvers import Resolver |
13 | 7 |
|
14 | 8 | __all__ = ["browser_router"] |
15 | 9 |
|
16 | | -ConversionFunc: TypeAlias = "Callable[[str], Any]" |
17 | | -ConverterMapping: TypeAlias = "dict[str, ConversionFunc]" |
18 | | - |
19 | | -STAR_PATTERN = re.compile("^.*$") |
20 | | -PARAM_PATTERN = re.compile(r"{(?P<name>\w+)(?P<type>:\w+)?}") |
21 | | - |
22 | | - |
23 | | -class SimpleResolver: |
24 | | - """A simple route resolver that uses regex to match paths""" |
25 | | - |
26 | | - def __init__(self, route: Route) -> None: |
27 | | - self.element = route.element |
28 | | - self.pattern, self.converters = parse_path(route.path) |
29 | | - self.key = self.pattern.pattern |
30 | | - |
31 | | - def resolve(self, path: str) -> tuple[Any, dict[str, Any]] | None: |
32 | | - match = self.pattern.match(path) |
33 | | - if match: |
34 | | - return ( |
35 | | - self.element, |
36 | | - {k: self.converters[k](v) for k, v in match.groupdict().items()}, |
37 | | - ) |
38 | | - return None |
39 | | - |
40 | | - |
41 | | -def parse_path(path: str) -> tuple[re.Pattern[str], ConverterMapping]: |
42 | | - if path == "*": |
43 | | - return STAR_PATTERN, {} |
44 | | - |
45 | | - pattern = "^" |
46 | | - last_match_end = 0 |
47 | | - converters: ConverterMapping = {} |
48 | | - for match in PARAM_PATTERN.finditer(path): |
49 | | - param_name = match.group("name") |
50 | | - param_type = (match.group("type") or "str").lstrip(":") |
51 | | - try: |
52 | | - param_conv = CONVERSION_TYPES[param_type] |
53 | | - except KeyError: |
54 | | - raise ValueError(f"Unknown conversion type {param_type!r} in {path!r}") |
55 | | - pattern += re.escape(path[last_match_end : match.start()]) |
56 | | - pattern += f"(?P<{param_name}>{param_conv['regex']})" |
57 | | - converters[param_name] = param_conv["func"] |
58 | | - last_match_end = match.end() |
59 | | - pattern += re.escape(path[last_match_end:]) + "$" |
60 | | - return re.compile(pattern), converters |
61 | | - |
62 | | - |
63 | | -class ConversionInfo(TypedDict): |
64 | | - """Information about a conversion type""" |
65 | | - |
66 | | - regex: str |
67 | | - """The regex to match the conversion type""" |
68 | | - func: ConversionFunc |
69 | | - """The function to convert the matched string to the expected type""" |
70 | | - |
71 | | - |
72 | | -CONVERSION_TYPES: dict[str, ConversionInfo] = { |
73 | | - "int": { |
74 | | - "regex": r"\d+", |
75 | | - "func": int, |
76 | | - }, |
77 | | - "str": { |
78 | | - "regex": r"[^/]+", |
79 | | - "func": str, |
80 | | - }, |
81 | | - "uuid": { |
82 | | - "regex": r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", |
83 | | - "func": uuid.UUID, |
84 | | - }, |
85 | | - "slug": { |
86 | | - "regex": r"[-a-zA-Z0-9_]+", |
87 | | - "func": str, |
88 | | - }, |
89 | | - "path": { |
90 | | - "regex": r".+", |
91 | | - "func": str, |
92 | | - }, |
93 | | - "float": { |
94 | | - "regex": r"\d+(\.\d+)?", |
95 | | - "func": float, |
96 | | - }, |
97 | | -} |
98 | | -"""The conversion types supported by the default Resolver. You can add more types if needed.""" |
99 | | - |
100 | 10 |
|
101 | | -browser_router = create_router(SimpleResolver) |
| 11 | +browser_router = create_router(Resolver) |
102 | 12 | """This is the recommended router for all ReactPy Router web projects. |
103 | 13 | It uses the DOM History API to update the URL and manage the history stack.""" |
104 | 14 | # TODO: Check if this is true. If not, make it true. |
0 commit comments