Skip to content

Commit e96a512

Browse files
committed
Change star pattern to {NAME:any}
1 parent 518d226 commit e96a512

File tree

12 files changed

+90
-69
lines changed

12 files changed

+90
-69
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ docs/site
44

55
# --- JAVASCRIPT BUNDLES ---
66

7-
src/reactpy_router/bundle.js
7+
src/reactpy_router/static/bundle.js
88

99
# --- PYTHON IGNORE FILES ----
1010

docs/examples/python/basic-routing-more-routes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ def root():
77
return browser_router(
88
route("/", html.h1("Home Page 🏠")),
99
route("/messages", html.h1("Messages 💬")),
10-
route("*", html.h1("Missing Link 🔗‍💥")),
10+
route("{404:any}", html.h1("Missing Link 🔗‍💥")),
1111
)
1212

1313

docs/examples/python/basic-routing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
def root():
77
return browser_router(
88
route("/", html.h1("Home Page 🏠")),
9-
route("*", html.h1("Missing Link 🔗‍💥")),
9+
route("{404:any}", html.h1("Missing Link 🔗‍💥")),
1010
)
1111

1212

docs/examples/python/nested-routes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def root():
2626
route("/with/Alice", messages_with("Alice")),
2727
route("/with/Alice-Bob", messages_with("Alice", "Bob")),
2828
),
29-
route("*", html.h1("Missing Link 🔗‍💥")),
29+
route("{404:any}", html.h1("Missing Link 🔗‍💥")),
3030
)
3131

3232

docs/examples/python/route-links.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ def root():
77
return browser_router(
88
route("/", home()),
99
route("/messages", html.h1("Messages 💬")),
10-
route("*", html.h1("Missing Link 🔗‍💥")),
10+
route("{404:any}", html.h1("Missing Link 🔗‍💥")),
1111
)
1212

1313

docs/examples/python/route-parameters.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def root():
2525
all_messages(),
2626
route("/with/{names}", messages_with()), # note the path param
2727
),
28-
route("*", html.h1("Missing Link 🔗‍💥")),
28+
route("{404:any}", html.h1("Missing Link 🔗‍💥")),
2929
)
3030

3131

docs/src/learn/routers-routes-and-links.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Here's a basic example showing how to use `#!python browser_router` with two rou
1919
{% include "../../examples/python/basic-routing.py" %}
2020
```
2121

22-
Here we'll note some special syntax in the route path for the second route. The `#!python "*"` is a wildcard that will match any path. This is useful for creating a "404" page that will be shown when no other route matches.
22+
Here we'll note some special syntax in the route path for the second route. The `#!python "any"` type is a wildcard that will match any path. This is useful for creating a default page or error page such as "404 NOT FOUND".
2323

2424
### Browser Router
2525

@@ -34,11 +34,13 @@ In this case, `#!python param` is the name of the route parameter and the option
3434

3535
| Type | Pattern |
3636
| --- | --- |
37-
| `#!python str` (default) | `#!python [^/]+` |
3837
| `#!python int` | `#!python \d+` |
39-
| `#!python float` | `#!python \d+(\.\d+)?` |
38+
| `#!python str` (default) | `#!python [^/]+` |
4039
| `#!python uuid` | `#!python [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}` |
40+
| `#!python slug` | `#!python [-a-zA-Z0-9_]+` |
4141
| `#!python path` | `#!python .+` |
42+
| `#!python float` | `#!python \d+(\.\d+)?` |
43+
| `#!python any` | `#!python .*` |
4244

4345
So in practice these each might look like:
4446

src/reactpy_router/converters.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,9 @@
2929
"regex": r"\d+(\.\d+)?",
3030
"func": float,
3131
},
32+
"any": {
33+
"regex": r".*",
34+
"func": str,
35+
},
3236
}
3337
"""The conversion types supported by the default Resolver. You can add more types if needed."""

src/reactpy_router/core.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,7 @@ def router_component(
6565
if match:
6666
route_elements = [
6767
_route_state_context(
68-
html.div(
69-
element, # type: ignore
70-
key=f"{location.pathname}{select}{params}{element}{id(element)}",
71-
),
68+
element,
7269
value=_RouteState(set_location, params),
7370
)
7471
for element, params in match

src/reactpy_router/resolvers.py

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,47 +16,63 @@ def __init__(
1616
self,
1717
route: Route,
1818
param_pattern=r"{(?P<name>\w+)(?P<type>:\w+)?}",
19-
match_any_identifier=r"\*$",
2019
converters: dict[str, ConversionInfo] | None = None,
2120
) -> None:
2221
self.element = route.element
23-
self.pattern, self.converter_mapping = self.parse_path(route.path)
2422
self.registered_converters = converters or CONVERTERS
25-
self.key = self.pattern.pattern
23+
self.converter_mapping: ConverterMapping = {}
24+
# self.match_any_indentifier = match_any_identifier
2625
self.param_regex = re.compile(param_pattern)
27-
self.match_any = match_any_identifier
26+
self.pattern = self.parse_path(route.path)
27+
self.key = self.pattern.pattern # Unique identifier for ReactPy rendering
2828

29-
def parse_path(self, path: str) -> tuple[re.Pattern[str], ConverterMapping]:
29+
def parse_path(self, path: str) -> re.Pattern[str]:
3030
# Convert path to regex pattern, then interpret using registered converters
3131
pattern = "^"
3232
last_match_end = 0
33-
converter_mapping: ConverterMapping = {}
33+
34+
# Iterate through matches of the parameter pattern
3435
for match in self.param_regex.finditer(path):
35-
param_name = match.group("name")
36+
# Extract parameter name and type
37+
name = match.group("name")
38+
if name[0].isnumeric():
39+
name = f"_numeric_{name}"
3640
param_type = (match.group("type") or "str").strip(":")
41+
42+
# Check if a converter exists for the type
3743
try:
38-
param_conv = self.registered_converters[param_type]
44+
conversion_info = self.registered_converters[param_type]
3945
except KeyError as e:
4046
raise ValueError(
4147
f"Unknown conversion type {param_type!r} in {path!r}"
4248
) from e
49+
50+
# Add the string before the match to the pattern
4351
pattern += re.escape(path[last_match_end : match.start()])
44-
pattern += f"(?P<{param_name}>{param_conv['regex']})"
45-
converter_mapping[param_name] = param_conv["func"]
52+
53+
# Add the match to the pattern
54+
pattern += f"(?P<{name}>{conversion_info['regex']})"
55+
56+
# Keep a local mapping of parameter names to conversion functions.
57+
self.converter_mapping[name] = conversion_info["func"]
58+
59+
# Update the last match end
4660
last_match_end = match.end()
47-
pattern += f"{re.escape(path[last_match_end:])}$"
4861

49-
# Replace "match anything" pattern with regex, if it's at the end of the path
50-
if pattern.endswith(self.match_any):
51-
pattern = f"{pattern[:-3]}.*$"
62+
# Add the string after the last match
63+
pattern += f"{re.escape(path[last_match_end:])}$"
5264

53-
return re.compile(pattern), converter_mapping
65+
return re.compile(pattern)
5466

5567
def resolve(self, path: str) -> tuple[Any, dict[str, Any]] | None:
5668
match = self.pattern.match(path)
5769
if match:
58-
return (
59-
self.element,
60-
{k: self.converter_mapping[k](v) for k, v in match.groupdict().items()},
61-
)
70+
# Convert the matched groups to the correct types
71+
params = {
72+
parameter_name.strip("_numeric_"): self.converter_mapping[
73+
parameter_name
74+
](value)
75+
for parameter_name, value in match.groupdict().items()
76+
}
77+
return (self.element, params)
6278
return None

0 commit comments

Comments
 (0)