render-engine 2025.9.1a2__py3-none-any.whl → 2025.10.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
render_engine/page.py CHANGED
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  import re
2
3
  from pathlib import Path
3
4
  from typing import Any
@@ -10,6 +11,8 @@ from render_engine.themes import ThemeManager
10
11
  from ._base_object import BaseObject
11
12
  from .plugins import PluginManager
12
13
 
14
+ logger = logging.getLogger("Page")
15
+
13
16
 
14
17
  class BasePage(BaseObject):
15
18
  """
@@ -28,6 +31,8 @@ class BasePage(BaseObject):
28
31
  extension (str): The file extension for the page. Defaults to ".html".
29
32
  routes (list[str] | Path): The list of routes for the page. Defaults to ["./"].
30
33
  template (str | Template | None): The template to use for rendering the page.
34
+ site: The Site object that owns the page.
35
+ no_prerender: Flag to not prerender the content
31
36
  """
32
37
 
33
38
  extension: str = ".html"
@@ -36,7 +41,8 @@ class BasePage(BaseObject):
36
41
  rendered_content: str | None
37
42
  _reference: str = "_slug"
38
43
  plugin_manager: PluginManager | None
39
- site = None
44
+ site = None # This is a Site but circular imports so we can't actually type hint it.
45
+ no_prerender: bool = False
40
46
 
41
47
  @property
42
48
  def _content(self) -> any:
@@ -69,20 +75,35 @@ class BasePage(BaseObject):
69
75
  return f"/{route}/{self.path_name}"
70
76
 
71
77
  def _render_from_template(self, template: Template, **kwargs) -> str:
72
- """Renders the page from a template."""
78
+ """
79
+ Renders the page from a template.
80
+
81
+ If the content looks like a template that
82
+
83
+ :param template: Template to render
84
+ :param **kwargs: Data to pass into the template for rendering.
85
+ :return: The rendered page
86
+ """
73
87
  template_data = {"data": self._data, "content": self._content}
74
88
  if site := getattr(self, "site", None):
75
89
  template_data["site_map"] = site.site_map
76
- if isinstance(self._content, str) and re.search(r"{{.*}}", self._content):
90
+ if not self.no_prerender and isinstance(self._content, str) and re.search(r"{{.*?site_map.*?}}", self._content):
77
91
  # If the content looks like a template, try to render it.
78
- content_template = Template(self._content)
79
- template_data["content"] = content_template.render(
80
- **{
81
- **self.to_dict(),
82
- **template_data,
83
- **kwargs,
84
- }
85
- )
92
+ try:
93
+ content_template = Template(self._content)
94
+ except Exception:
95
+ logger.info(f"Failed to parse {repr(self.path_name)} as a template.", exc_info=True)
96
+ else:
97
+ try:
98
+ template_data["content"] = content_template.render(
99
+ **{
100
+ **self.to_dict(),
101
+ **template_data,
102
+ **kwargs,
103
+ }
104
+ )
105
+ except Exception:
106
+ logger.info(f"Failed to pre-render {repr(self.path_name)}.", exc_info=True)
86
107
 
87
108
  return template.render(
88
109
  **{
@@ -1,6 +1,7 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3
- {% for item in items %}
3
+ {% for item in site_map %}
4
4
  {% include 'sitemap_item.xml' %}
5
+
5
6
  {% endfor %}
6
7
  </urlset>
@@ -1,10 +1,10 @@
1
1
  <url>
2
- <loc>{{SITE_URL}}/{{item}}</loc>
2
+ <loc>{{SITE_URL.rstrip("/")}}/{{item.url_for.lstrip("/")}}</loc>
3
3
  {% if item.lastmod %}
4
4
  <lastmod>{{item.lastmod}}</lastmod>
5
5
  {% endif %}
6
6
  {% if item.changefreq %}
7
- <schangefreq>{{item.changefreq}}</changefreq>
7
+ <changefreq>{{item.changefreq}}</changefreq>
8
8
  {% endif %}
9
9
  {% if item.priority %}
10
10
  <priority>{{item.priority}}</priority>
render_engine/site.py CHANGED
@@ -21,7 +21,8 @@ class Site:
21
21
  Attributes:
22
22
  site_vars (dict): A dictionary containing site-wide variables and their values.
23
23
  plugin_settings (dict): A dictionary containing plugin settings.
24
- render_site_map (bool): Whether to render the generated site map as a page.
24
+ render_html_site_map (bool): Whether to render the generated site map as an HTML page.
25
+ render_xml_site_map (bool): Whether to render the generated site map as XML.
25
26
 
26
27
  Methods:
27
28
  update_site_vars(**kwargs): Updates the site-wide variables with the given key-value pairs.
@@ -51,7 +52,8 @@ class Site:
51
52
  _template_path: str | Path = "templates"
52
53
  _static_paths: set = {"static"}
53
54
  plugin_settings: dict = {"plugins": defaultdict(dict)}
54
- render_site_map: bool = False
55
+ render_html_site_map: bool = False
56
+ render_xml_site_map: bool = False
55
57
 
56
58
  def __init__(
57
59
  self,
@@ -253,7 +255,7 @@ class Site:
253
255
  with Progress() as progress:
254
256
  task_site_map = progress.add_task("Generating site map", total=1)
255
257
  self._site_map = SiteMap(self.route_list, self.site_vars.get("SITE_URL", ""))
256
- if self.render_site_map:
258
+ if self.render_html_site_map:
257
259
 
258
260
  @self.page
259
261
  class SiteMapPage(Page):
@@ -262,6 +264,13 @@ class Site:
262
264
  content = self._site_map.html
263
265
  template = "page.html"
264
266
 
267
+ if self.render_xml_site_map:
268
+
269
+ @self.page
270
+ class SiteMapXml(Page):
271
+ path_name = "site_map.xml"
272
+ template = "sitemap.xml"
273
+
265
274
  progress.update(task_site_map, advance=1)
266
275
 
267
276
  pre_build_task = progress.add_task("Loading Pre-Build Plugins and Themes", total=1)
render_engine/site_map.py CHANGED
@@ -22,7 +22,7 @@ class SiteMapEntry:
22
22
  self._route = f"/{route.lstrip('/')}/{self.path_name}" if from_collection else f"/{self.path_name}"
23
23
  self.entries = list()
24
24
  case Collection():
25
- self._route = f"/{route.lstrip('/')}"
25
+ self._route = f"/{entry.routes[0].lstrip('/')}"
26
26
  self.entries = [
27
27
  SiteMapEntry(collection_entry, self._route, from_collection=True) for collection_entry in entry
28
28
  ]
@@ -34,6 +34,10 @@ class SiteMapEntry:
34
34
  """The URL for the given entry"""
35
35
  return str(self._route)
36
36
 
37
+ def __str__(self) -> str:
38
+ """String representation of the entry as its URL"""
39
+ return self.url_for
40
+
37
41
 
38
42
  class SiteMap:
39
43
  """Site map"""
@@ -58,6 +62,12 @@ class SiteMap:
58
62
  self._collections[sm_entry.slug] = sm_entry
59
63
  self.site_url = site_url
60
64
 
65
+ def __iter__(self):
66
+ """Iterator for the site map object"""
67
+ for entry in self._route_map.values():
68
+ yield entry
69
+ yield from entry.entries
70
+
61
71
  def find(
62
72
  self,
63
73
  value: str,
@@ -121,6 +131,8 @@ class SiteMap:
121
131
  def html(self) -> str:
122
132
  """Build the site map as HTML"""
123
133
  html_string = "<ul>\n"
134
+ # We can't iterate over `self` because that will flatten out the site map and we do not want that for the HTML
135
+ # version.
124
136
  for entry in self._route_map.values():
125
137
  html_string += f'\t<li><a href="{urljoin(self.site_url, entry.url_for)}">{entry.title}</a></li>\n'
126
138
  if entry.entries:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: render_engine
3
- Version: 2025.9.1a2
3
+ Version: 2025.10.1
4
4
  Summary: A Flexible Static Site Generator for Python
5
5
  Project-URL: homepage, https://github.com/render-engine/render-engine/
6
6
  Project-URL: repository, https://github.com/render-engine/render-engine/
@@ -9,11 +9,11 @@ render_engine/engine.py,sha256=GOtUiq4ny5GHaLSCeH5u1Zk1JnWJVh63vK7etJiwS20,2843
9
9
  render_engine/feeds.py,sha256=i-VHsb6pRplMzaenBn6oeqh9yI_N4WVUAExPox6iJgw,921
10
10
  render_engine/hookspecs.py,sha256=GhOpw0zTQjfwWOFYYbJ4P7Cvq-oy1MmTPHmd90dr0kg,2292
11
11
  render_engine/links.py,sha256=pKmQMTz8-yGX8IecHcrlF3Dkejk7cptaO3qCkQiHB9I,2560
12
- render_engine/page.py,sha256=mIKdOmyQ1zRcaoZej9hMV2xajH_frXllaO-ZpZMzCtM,8091
12
+ render_engine/page.py,sha256=l6sKWNJ4gBtC_ONEc0u479q3znL-8Q7U_phNqXqmh6w,8988
13
13
  render_engine/plugins.py,sha256=NXM8QTbbRV-DwgpQRoIhILijJBN4SyYg2Rkk1LUAuZM,4703
14
14
  render_engine/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- render_engine/site.py,sha256=86C-LbmONbhmLFDn7uKZokcMpsm6Vd3YcIxaxw1bbjk,12590
16
- render_engine/site_map.py,sha256=E-Bw563azKHGyACUWe0h48HhCLkJhkKR7V5JKhPLZoo,4975
15
+ render_engine/site.py,sha256=URvG7OQSgeFOcrZwrD8vHLUWa6UxDBxIP-UMeLLqhZM,12936
16
+ render_engine/site_map.py,sha256=I1p_yMDMy1jpIivgNgZptnsxZa8NkcrpciVJE3DlFlQ,5422
17
17
  render_engine/themes.py,sha256=TFG1rd34QCBvBWfeDbawgsn6kprmjsDTa1pdDSwDMic,4207
18
18
  render_engine/extras/__init__.py,sha256=L4jr4A7Jl-ODnSx1q2fP3_dBo37Dw6yepNRddu1nFNo,72
19
19
  render_engine/parsers/markdown.py,sha256=0jpixCaoHaL0IRSvFIljJIRCvFkXoKTEYQNK38LwMDU,287
@@ -24,15 +24,15 @@ render_engine/render_engine_templates/base_collection_path.md,sha256=7PIn5Oxei-b
24
24
  render_engine/render_engine_templates/page.html,sha256=p9aZ6_6mouPT1c8FVg-f14MeSt4OB-sH3hcXTY6jGmA,42
25
25
  render_engine/render_engine_templates/rss2.0.xml,sha256=Z5LGUdPRr3fQhXtJCjR8uxiPQ_GqYHELleDaS8Tr1oU,1803
26
26
  render_engine/render_engine_templates/rss2.0_items.xml,sha256=jxhDG9K2R112-upqq4NZnahDGZcvlWIn_sl9b4ArVEI,1306
27
- render_engine/render_engine_templates/sitemap.xml,sha256=A4zhyx2Z9RLTwTVyvK5kKpnXGRMJ4xwkf_ueI05Z5Hs,180
28
- render_engine/render_engine_templates/sitemap_item.xml,sha256=ofZDB8JSrtNEw4Lq2uHgnhX1xll0N_SaXfVJiJlmmg8,283
27
+ render_engine/render_engine_templates/sitemap.xml,sha256=wqtVHwNBLBJrbfScJELHyf2VC3YxjL_cPgnI1xlb67U,184
28
+ render_engine/render_engine_templates/sitemap_item.xml,sha256=8pQ1IHeGO2kDuKhxbQMh8HoDTuaKewtHX2NGt3ztGBY,314
29
29
  render_engine/render_engine_templates/base_templates/_archive.html,sha256=eFd62LkMcdxz70g-ksmCmTr2TSnKt2Z2X8zx80JpAPk,189
30
30
  render_engine/render_engine_templates/base_templates/_base.html,sha256=piaDrWjE1gG0IvUzpaH1RmHsTeRheeG3S9YmTdIf-CI,710
31
31
  render_engine/render_engine_templates/base_templates/_page.html,sha256=jjrY2BAwlJeVSNF4Pa6rd4Kcg0dfYpPpieZE2zeMTOM,163
32
32
  render_engine/render_engine_templates/components/footer.html,sha256=HkPGGhfN0HcYm7t8zgXWCQ3bsCbT8FxT4_n2-9e1zUE,74
33
33
  render_engine/render_engine_templates/components/page_title.html,sha256=l8aE1TY94UPHXHqAyy6jv4IoN2Hv9cbrTPh7ILkMyxg,137
34
34
  render_engine/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
- render_engine-2025.9.1a2.dist-info/METADATA,sha256=FQAI3nwN7L-fxj7rzgMGDFgIpT6IHjQaScLnoyeHnkg,11894
36
- render_engine-2025.9.1a2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
- render_engine-2025.9.1a2.dist-info/top_level.txt,sha256=aNGALDMsFyrusho04AvUjSivsgEE9tQp_LP_jGr312Q,14
38
- render_engine-2025.9.1a2.dist-info/RECORD,,
35
+ render_engine-2025.10.1.dist-info/METADATA,sha256=MMMnXU-jA0JjG9lKbsa-dO5c35Wc4X450rC2_Cstdic,11893
36
+ render_engine-2025.10.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
+ render_engine-2025.10.1.dist-info/top_level.txt,sha256=aNGALDMsFyrusho04AvUjSivsgEE9tQp_LP_jGr312Q,14
38
+ render_engine-2025.10.1.dist-info/RECORD,,