Skip to content

Commit 46ef879

Browse files
authored
add sitemap slash configuration (#6291)
* add sitemap slash configuration * respond to greptile comments
1 parent 57b30bc commit 46ef879

File tree

2 files changed

+235
-17
lines changed

2 files changed

+235
-17
lines changed

packages/reflex-base/src/reflex_base/plugins/sitemap.py

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import datetime
44
from collections.abc import Sequence
5+
from dataclasses import dataclass
56
from pathlib import Path
67
from types import SimpleNamespace
78
from typing import TYPE_CHECKING, Literal, TypedDict
@@ -16,6 +17,8 @@
1617
if TYPE_CHECKING:
1718
from reflex.app import UnevaluatedPage
1819

20+
TrailingSlashOption = Literal["always", "never", "preserve"]
21+
1922
Location = str
2023
LastModified = datetime.datetime
2124
ChangeFrequency = Literal[
@@ -49,20 +52,30 @@ class Constants(SimpleNamespace):
4952

5053

5154
def configuration_with_loc(
52-
*, config: SitemapLinkConfiguration, deploy_url: str | None, loc: Location
55+
*,
56+
config: SitemapLinkConfiguration,
57+
deploy_url: str | None,
58+
loc: Location,
59+
trailing_slash: TrailingSlashOption,
5360
) -> SitemapLink:
5461
"""Set the 'loc' field of the configuration.
5562
5663
Args:
5764
config: The configuration dictionary.
5865
deploy_url: The deployment URL, if any.
5966
loc: The location to set.
67+
trailing_slash: Option for handling trailing slashes in URLs.
6068
6169
Returns:
6270
A SitemapLink dictionary with the 'loc' field set.
6371
"""
6472
if deploy_url and not loc.startswith("http://") and not loc.startswith("https://"):
6573
loc = f"{deploy_url.rstrip('/')}/{loc.lstrip('/')}"
74+
if trailing_slash == "always" and not loc.endswith("/"):
75+
loc += "/"
76+
elif trailing_slash == "never":
77+
stripped = loc.rstrip("/")
78+
loc = stripped or loc
6679
link: SitemapLink = {"loc": loc}
6780
if (lastmod := config.get("lastmod")) is not None:
6881
link["lastmod"] = lastmod
@@ -121,11 +134,13 @@ def is_route_dynamic(route: str) -> bool:
121134

122135
def generate_links_for_sitemap(
123136
unevaluated_pages: Sequence["UnevaluatedPage"],
137+
trailing_slash: TrailingSlashOption,
124138
) -> list[SitemapLink]:
125139
"""Generate sitemap links from unevaluated pages.
126140
127141
Args:
128142
unevaluated_pages: Sequence of unevaluated pages.
143+
trailing_slash: Option for handling trailing slashes in URLs.
129144
130145
Returns:
131146
A list of SitemapLink dictionaries.
@@ -159,52 +174,67 @@ def generate_links_for_sitemap(
159174
continue
160175

161176
sitemap_link = configuration_with_loc(
162-
config=sitemap_config, deploy_url=deploy_url, loc=loc
177+
config=sitemap_config,
178+
deploy_url=deploy_url,
179+
loc=loc,
180+
trailing_slash=trailing_slash,
163181
)
164182

165183
elif (loc := sitemap_config.get("loc")) is not None:
166184
sitemap_link = configuration_with_loc(
167-
config=sitemap_config, deploy_url=deploy_url, loc=loc
185+
config=sitemap_config,
186+
deploy_url=deploy_url,
187+
loc=loc,
188+
trailing_slash=trailing_slash,
168189
)
169190

170191
else:
171192
loc = page.route if page.route != "index" else "/"
172193
if not loc.startswith("/"):
173194
loc = "/" + loc
174195
sitemap_link = configuration_with_loc(
175-
config=sitemap_config, deploy_url=deploy_url, loc=loc
196+
config=sitemap_config,
197+
deploy_url=deploy_url,
198+
loc=loc,
199+
trailing_slash=trailing_slash,
176200
)
177201

178202
links.append(sitemap_link)
179203
return links
180204

181205

182-
def sitemap_task(unevaluated_pages: Sequence["UnevaluatedPage"]) -> tuple[str, str]:
206+
def sitemap_task(
207+
unevaluated_pages: Sequence["UnevaluatedPage"], trailing_slash: TrailingSlashOption
208+
) -> tuple[str, str]:
183209
"""Task to generate the sitemap XML file.
184210
185211
Args:
186212
unevaluated_pages: Sequence of unevaluated pages.
213+
trailing_slash: Option for handling trailing slashes in URLs.
187214
188215
Returns:
189216
A tuple containing the file path and the generated XML content.
190217
"""
191218
return (
192219
str(Constants.FILE_PATH),
193-
generate_xml(generate_links_for_sitemap(unevaluated_pages)),
220+
generate_xml(generate_links_for_sitemap(unevaluated_pages, trailing_slash)),
194221
)
195222

196223

224+
@dataclass(kw_only=True, frozen=True)
197225
class SitemapPlugin(PluginBase):
198226
"""Sitemap plugin for Reflex."""
199227

228+
trailing_slash: TrailingSlashOption = "preserve"
229+
200230
def pre_compile(self, **context):
201231
"""Generate the sitemap XML file before compilation.
202232
203233
Args:
204234
context: The context for the plugin.
205235
"""
206236
unevaluated_pages = context.get("unevaluated_pages", [])
207-
context["add_save_task"](sitemap_task, unevaluated_pages)
237+
context["add_save_task"](sitemap_task, unevaluated_pages, self.trailing_slash)
208238

209239

210240
Plugin = SitemapPlugin

0 commit comments

Comments
 (0)