Skip to content

Commit

Permalink
Support star pattern in URL router (#218)
Browse files Browse the repository at this point in the history
  • Loading branch information
Archmonger authored Jan 30, 2024
1 parent e3d0866 commit 0bd12d1
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 26 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Using the following categories, list your changes in this order:
### Added

- An "offline component" can now be displayed when the client disconnects from the server.
- URL router now supports a `*` wildcard to create default routes.

## [3.6.0] - 2024-01-10

Expand Down
3 changes: 2 additions & 1 deletion docs/examples/python/django-router.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ def my_component():
route("/router/slug/<slug:value>/", html.div("Example 5")),
route("/router/string/<str:value>/", html.div("Example 6")),
route("/router/uuid/<uuid:value>/", html.div("Example 7")),
route("/router/two_values/<int:value>/<str:value2>/", html.div("Example 9")),
route("/router/two_values/<int:value>/<str:value2>/", html.div("Example 8")),
route("/router/*", html.div("Fallback")),
)
6 changes: 4 additions & 2 deletions src/reactpy_django/router/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from reactpy_django.router.components import django_router
from reactpy_router.core import create_router

__all__ = ["django_router"]
from reactpy_django.router.resolvers import DjangoResolver

django_router = create_router(DjangoResolver)
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import re
from typing import Any

from reactpy_router.core import create_router
from reactpy_router.simple import ConverterMapping
from reactpy_router.types import Route

Expand Down Expand Up @@ -33,6 +32,7 @@ def resolve(self, path: str) -> tuple[Any, dict[str, Any]] | None:

# TODO: Make reactpy_router's parse_path generic enough to where we don't have to define our own
def parse_path(path: str) -> tuple[re.Pattern[str], ConverterMapping]:
# Convert path to regex pattern, and make sure to interpret the registered converters (ex. <int:foo>)
pattern = "^"
last_match_end = 0
converters: ConverterMapping = {}
Expand All @@ -50,7 +50,9 @@ def parse_path(path: str) -> tuple[re.Pattern[str], ConverterMapping]:
converters[param_name] = param_conv["func"]
last_match_end = match.end()
pattern += f"{re.escape(path[last_match_end:])}$"
return re.compile(pattern), converters

# Replace literal `*` with "match anything" regex pattern, if it's at the end of the path
if pattern.endswith("\*$"):
pattern = f"{pattern[:-3]}.*$"

django_router = create_router(DjangoResolver)
return re.compile(pattern), converters
43 changes: 23 additions & 20 deletions tests/test_app/router/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,14 @@


@component
def display_params(*args):
params = use_params()
return html._(
html.div(f"Params: {params}"),
*args,
)


@component
def main():
def display_params(string: str):
location = use_location()
query = use_query()
params = use_params()

route_info = html._(
return html._(
html.div({"id": "router-string"}, string),
html.div(f"Params: {params}"),
html.div(
{"id": "router-path", "data-path": location.pathname},
f"Path Name: {location.pathname}",
Expand All @@ -26,17 +20,26 @@ def main():
html.div(f"Query: {query}"),
)


@component
def main():
return django_router(
route("/router/", html.div("Path 1", route_info)),
route("/router/any/<value>/", display_params("Path 2", route_info)),
route("/router/integer/<int:value>/", display_params("Path 3", route_info)),
route("/router/path/<path:value>/", display_params("Path 4", route_info)),
route("/router/slug/<slug:value>/", display_params("Path 5", route_info)),
route("/router/string/<str:value>/", display_params("Path 6", route_info)),
route("/router/uuid/<uuid:value>/", display_params("Path 7", route_info)),
route("/router/", None, route("abc/", display_params("Path 8", route_info))),
route("/router/", display_params("Path 1")),
route("/router/any/<value>/", display_params("Path 2")),
route("/router/integer/<int:value>/", display_params("Path 3")),
route("/router/path/<path:value>/", display_params("Path 4")),
route("/router/slug/<slug:value>/", display_params("Path 5")),
route("/router/string/<str:value>/", display_params("Path 6")),
route("/router/uuid/<uuid:value>/", display_params("Path 7")),
route("/router/", None, route("abc/", display_params("Path 8"))),
route(
"/router/two/<int:value>/<str:value2>/",
display_params("Path 9", route_info),
display_params("Path 9"),
),
route(
"/router/star/",
None,
route("one/", display_params("Path 11")),
route("*", display_params("Path 12")),
),
)
15 changes: 15 additions & 0 deletions tests/test_app/tests/test_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,21 @@ def test_url_router(self):
path = new_page.wait_for_selector("#router-path")
self.assertIn("/router/two/123/abc/", path.get_attribute("data-path"))

new_page.goto(f"{self.live_server_url}/router/star/one/")
path = new_page.wait_for_selector("#router-path")
self.assertIn("/router/star/one/", path.get_attribute("data-path"))

new_page.goto(
f"{self.live_server_url}/router/star/adslkjgklasdjhfah/6789543256/"
)
path = new_page.wait_for_selector("#router-path")
self.assertIn(
"/router/star/adslkjgklasdjhfah/6789543256/",
path.get_attribute("data-path"),
)
string = new_page.query_selector("#router-string")
self.assertEquals("Path 12", string.text_content())

finally:
new_page.close()

Expand Down

0 comments on commit 0bd12d1

Please sign in to comment.