render-engine 2025.9.1a1__py3-none-any.whl → 2025.10.1a1__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.
@@ -26,6 +26,7 @@ class BaseObject:
26
26
  template_vars: dict
27
27
  plugins: list[Callable] | None
28
28
  plugin_settings: dict = {"plugins": defaultdict(dict)}
29
+ skip_site_map: bool = False
29
30
 
30
31
  @property
31
32
  def _title(self) -> str:
@@ -126,3 +127,7 @@ class BaseObject:
126
127
  base_dict[key] = value
127
128
 
128
129
  return base_dict
130
+
131
+ def render(self, *args, **kwargs):
132
+ """Render method. Implemented in child objects"""
133
+ raise NotImplementedError(f'{self.__class__.__name__} does not implement the "render" method.')
@@ -252,6 +252,33 @@ class Collection(BaseObject):
252
252
  return
253
253
  method(collection=self, site=site, settings=self.plugin_manager.plugin_settings)
254
254
 
255
+ def render(self) -> None:
256
+ """Iterate through Pages and Check for Collections and Feeds"""
257
+
258
+ for entry in self:
259
+ entry.plugin_manager = copy.deepcopy(self.plugin_manager)
260
+
261
+ for route in entry.routes:
262
+ entry.site = self.site
263
+ entry.render(route, self.site.theme_manager)
264
+
265
+ if getattr(self, "has_archive", False):
266
+ for archive in self.archives:
267
+ archive.site = self.site
268
+ logging.debug("Adding Archive: %s", archive.__class__.__name__)
269
+
270
+ for _ in self.routes:
271
+ archive.render(self.routes[0], self.site.theme_manager)
272
+
273
+ if archive.is_index:
274
+ archive.slug = "index"
275
+ archive.render(self.routes[0], self.site.theme_manager)
276
+ feed: RSSFeed
277
+ if hasattr(self, "Feed"):
278
+ feed = self.feed
279
+ feed.site = self.site
280
+ feed.render(route="./", theme_manager=self.site.theme_manager)
281
+
255
282
 
256
283
  def render_archives(archive, **kwargs) -> list[Archive]:
257
284
  return [archive.render(pages=archive.pages, **kwargs) for archive in archive]
render_engine/page.py CHANGED
@@ -1,9 +1,12 @@
1
+ import re
1
2
  from pathlib import Path
2
3
  from typing import Any
3
4
 
4
5
  from jinja2 import Environment, Template
5
6
  from render_engine_parser.base_parsers import BasePageParser
6
7
 
8
+ from render_engine.themes import ThemeManager
9
+
7
10
  from ._base_object import BaseObject
8
11
  from .plugins import PluginManager
9
12
 
@@ -33,6 +36,7 @@ class BasePage(BaseObject):
33
36
  rendered_content: str | None
34
37
  _reference: str = "_slug"
35
38
  plugin_manager: PluginManager | None
39
+ site = None
36
40
 
37
41
  @property
38
42
  def _content(self) -> any:
@@ -66,10 +70,24 @@ class BasePage(BaseObject):
66
70
 
67
71
  def _render_from_template(self, template: Template, **kwargs) -> str:
68
72
  """Renders the page from a template."""
73
+ template_data = {"data": self._data, "content": self._content}
74
+ if site := getattr(self, "site", None):
75
+ template_data["site_map"] = site.site_map
76
+ if isinstance(self._content, str) and re.search(r"{{.*}}", self._content):
77
+ # 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
+ )
86
+
69
87
  return template.render(
70
88
  **{
71
89
  **self.to_dict(),
72
- **{"content": self._content, "data": self._data},
90
+ **template_data,
73
91
  **kwargs,
74
92
  },
75
93
  )
@@ -110,6 +128,21 @@ class BasePage(BaseObject):
110
128
  def __repr__(self) -> str:
111
129
  return f"<Page: {self._title}>"
112
130
 
131
+ def render(self, route: str | Path, theme_manager: ThemeManager) -> int:
132
+ """Render the page to the file system"""
133
+ path = Path(self.site.output_path) / Path(route) / Path(self.path_name)
134
+ path.parent.mkdir(parents=True, exist_ok=True)
135
+ settings = dict()
136
+ if (pm := getattr(self, "plugin_manager", None)) and pm is not None:
137
+ settings = {**self.site.plugin_manager.plugin_settings, "route": route}
138
+ pm.hook.render_content(page=self, settings=settings, site=self.site)
139
+ self.rendered_content = self._render_content(theme_manager.engine)
140
+ # pass the route to the plugin settings
141
+ if pm is not None:
142
+ pm.hook.post_render_content(page=self.__class__, settings=settings, site=self.site)
143
+
144
+ return path.write_text(self.rendered_content)
145
+
113
146
 
114
147
  class Page(BasePage):
115
148
  """
@@ -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
@@ -6,11 +6,11 @@ from pathlib import Path
6
6
  from jinja2 import FileSystemLoader, PrefixLoader
7
7
  from rich.progress import Progress
8
8
 
9
- from .archive import Archive
10
9
  from .collection import Collection
11
10
  from .engine import engine
12
11
  from .page import Page
13
12
  from .plugins import PluginManager, handle_plugin_registration
13
+ from .site_map import SiteMap
14
14
  from .themes import Theme, ThemeManager
15
15
 
16
16
 
@@ -21,6 +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_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.
24
26
 
25
27
  Methods:
26
28
  update_site_vars(**kwargs): Updates the site-wide variables with the given key-value pairs.
@@ -50,6 +52,8 @@ class Site:
50
52
  _template_path: str | Path = "templates"
51
53
  _static_paths: set = {"static"}
52
54
  plugin_settings: dict = {"plugins": defaultdict(dict)}
55
+ render_html_site_map: bool = False
56
+ render_xml_site_map: bool = False
53
57
 
54
58
  def __init__(
55
59
  self,
@@ -66,6 +70,7 @@ class Site:
66
70
  self.theme_manager.engine.globals.update(self.site_vars)
67
71
  if self.theme_manager.engine.loader is not None:
68
72
  self.theme_manager.engine.loader.loaders.insert(0, FileSystemLoader(self._template_path))
73
+ self._site_map = None
69
74
 
70
75
  @property
71
76
  def output_path(self) -> Path | str:
@@ -83,6 +88,10 @@ class Site:
83
88
  def static_paths(self, static_paths: set) -> None:
84
89
  self.theme_manager.static_paths = static_paths
85
90
 
91
+ @property
92
+ def site_map(self) -> SiteMap:
93
+ return self._site_map
94
+
86
95
  def update_site_vars(self, **kwargs) -> None:
87
96
  self.site_vars.update(**kwargs)
88
97
  self.theme_manager.engine.globals.update(self.site_vars)
@@ -204,46 +213,6 @@ class Site:
204
213
  self.route_list[getattr(page, page._reference)] = page
205
214
  return page
206
215
 
207
- def _render_output(self, route: str | Path, page: Page | Archive) -> int:
208
- """writes the page object to disk"""
209
- path = Path(self.output_path) / Path(route) / Path(page.path_name)
210
- path.parent.mkdir(parents=True, exist_ok=True)
211
- settings = dict()
212
-
213
- if hasattr(page, "plugin_manager") and page.plugin_manager is not None:
214
- settings = {**self.plugin_manager.plugin_settings, "route": route}
215
- page.plugin_manager.hook.render_content(page=page, settings=settings, site=self)
216
- page.rendered_content = page._render_content(engine=self.theme_manager.engine)
217
- # pass the route to the plugin settings
218
-
219
- if hasattr(page, "plugin_manager") and page.plugin_manager is not None:
220
- page.plugin_manager.hook.post_render_content(page=page.__class__, settings=settings, site=self)
221
-
222
- return path.write_text(page.rendered_content)
223
-
224
- def _render_full_collection(self, collection: Collection) -> None:
225
- """Iterate through Pages and Check for Collections and Feeds"""
226
-
227
- for entry in collection:
228
- entry.plugin_manager = copy.deepcopy(self.plugin_manager)
229
-
230
- for route in entry.routes:
231
- self._render_output(route, entry)
232
-
233
- if getattr(collection, "has_archive", False):
234
- for archive in collection.archives:
235
- logging.debug("Adding Archive: %s", archive.__class__.__name__)
236
-
237
- for route in collection.routes:
238
- self._render_output(collection.routes[0], archive)
239
-
240
- if archive.is_index:
241
- archive.slug = "index"
242
- self._render_output(collection.routes[0], archive)
243
-
244
- if hasattr(collection, "Feed"):
245
- self._render_output("./", collection.feed)
246
-
247
216
  def load_themes(self) -> None:
248
217
  """
249
218
  function for registering the themes with the theme_manager.
@@ -284,6 +253,26 @@ class Site:
284
253
  """
285
254
 
286
255
  with Progress() as progress:
256
+ task_site_map = progress.add_task("Generating site map", total=1)
257
+ self._site_map = SiteMap(self.route_list, self.site_vars.get("SITE_URL", ""))
258
+ if self.render_html_site_map:
259
+
260
+ @self.page
261
+ class SiteMapPage(Page):
262
+ title = f"{self.site_vars.get('SITE_TITLE', '')} Site Map"
263
+ path_name = "site_map.html"
264
+ content = self._site_map.html
265
+ template = "page.html"
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
+
274
+ progress.update(task_site_map, advance=1)
275
+
287
276
  pre_build_task = progress.add_task("Loading Pre-Build Plugins and Themes", total=1)
288
277
  self.plugin_manager.hook.pre_build_site(
289
278
  site=self,
@@ -302,32 +291,36 @@ class Site:
302
291
  self.theme_manager.engine.globals["routes"] = self.route_list
303
292
 
304
293
  for slug, entry in self.route_list.items():
294
+ entry.site = self
305
295
  progress.update(task_add_route, description=f"[blue]Adding[gold]Route: [blue]{slug}")
306
- if isinstance(entry, Page):
307
- for route in entry.routes:
296
+ args = []
297
+ match entry:
298
+ case Page():
299
+ for route in entry.routes:
300
+ progress.update(
301
+ task_add_route,
302
+ description=f"[blue]Adding[gold]Route: [blue]{entry._slug}",
303
+ )
304
+
305
+ # self._render_output(route, entry)
306
+ args = [route, self.theme_manager]
307
+ case Collection():
308
308
  progress.update(
309
309
  task_add_route,
310
- description=f"[blue]Adding[gold]Route: [blue]{entry._slug}",
310
+ description=f"[blue]Adding[gold]Route: [blue]Collection {entry._slug}",
311
311
  )
312
- self._render_output(route, entry)
312
+ pre_build_collection_task = progress.add_task(
313
+ "Loading Pre-Build-Collection Plugins",
314
+ total=1,
315
+ )
316
+ entry._run_collection_plugins(
317
+ hook_type="pre_build_collection",
318
+ site=self,
319
+ )
320
+ progress.update(pre_build_collection_task, advance=1)
313
321
 
322
+ entry.render(*args)
314
323
  if isinstance(entry, Collection):
315
- progress.update(
316
- task_add_route,
317
- description=f"[blue]Adding[gold]Route: [blue]Collection {entry._slug}",
318
- )
319
- pre_build_collection_task = progress.add_task(
320
- "Loading Pre-Build-Collection Plugins",
321
- total=1,
322
- )
323
- entry._run_collection_plugins(
324
- hook_type="pre_build_collection",
325
- site=self,
326
- )
327
- progress.update(pre_build_collection_task, advance=1)
328
-
329
- self._render_full_collection(entry)
330
-
331
324
  post_build_collection_task = progress.add_task(
332
325
  "Loading Post-Build-Collection Plugins",
333
326
  total=1,
@@ -0,0 +1,146 @@
1
+ from collections.abc import Iterable
2
+ from pathlib import Path
3
+ from urllib.parse import urljoin
4
+
5
+ import slugify
6
+
7
+ from render_engine import Collection, Page
8
+ from render_engine._base_object import BaseObject
9
+
10
+
11
+ class SiteMapEntry:
12
+ """Entry in the site map"""
13
+
14
+ def __init__(self, entry: BaseObject, route: str | Path, from_collection=False):
15
+ """Initialize the entry"""
16
+ self.slug = entry._slug
17
+ self.title = entry._title
18
+ self.path_name = entry.path_name
19
+ match entry:
20
+ case Page():
21
+ # For a base page the _route created if we use the route is invalid - just use the path_name
22
+ self._route = f"/{route.lstrip('/')}/{self.path_name}" if from_collection else f"/{self.path_name}"
23
+ self.entries = list()
24
+ case Collection():
25
+ self._route = f"/{route.lstrip('/')}"
26
+ self.entries = [
27
+ SiteMapEntry(collection_entry, self._route, from_collection=True) for collection_entry in entry
28
+ ]
29
+ case _:
30
+ pass
31
+
32
+ @property
33
+ def url_for(self) -> str:
34
+ """The URL for the given entry"""
35
+ return str(self._route)
36
+
37
+ def __str__(self) -> str:
38
+ """String representation of the entry as its URL"""
39
+ return self.url_for
40
+
41
+
42
+ class SiteMap:
43
+ """Site map"""
44
+
45
+ def __init__(self, route_list: dict, site_url: str):
46
+ """
47
+ Create the site map based on the route_list
48
+
49
+ :param route_list: The route list to parse
50
+ :param site_url: Used for rendering the HTML to have absolute URLs.
51
+ """
52
+ self._route_map = dict()
53
+ self._collections = dict()
54
+ route: str
55
+ entry: BaseObject
56
+ for route, entry in route_list.items():
57
+ if entry.skip_site_map:
58
+ continue
59
+ sm_entry = SiteMapEntry(entry, route)
60
+ self._route_map[sm_entry.slug] = sm_entry
61
+ if sm_entry.entries:
62
+ self._collections[sm_entry.slug] = sm_entry
63
+ self.site_url = site_url
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
+
71
+ def find(
72
+ self,
73
+ value: str,
74
+ attr: str = "slug",
75
+ collection: str = None,
76
+ full_search: bool = False,
77
+ ) -> SiteMapEntry | None:
78
+ """
79
+ Find an entry in the site map
80
+
81
+ Only one of the parameters slug, path_name, or title may be set.
82
+ If collection is set that collection will be searched. If it is not set then all collections
83
+ will be searched if full_search is True.
84
+ If there would be a match in multiple collections - or for just a page, the first match will be returned.
85
+
86
+ :param value: The value of attribute to search for
87
+ :param attr: The name of attribute to search for, defaults to slug
88
+ :param collection: The name of the collection to search
89
+ :param full_search: Search recursively in collections
90
+ :return: The first found match or None if not found
91
+ """
92
+
93
+ def search(s_attr: str, s_value: str, entries: Iterable) -> SiteMapEntry | None:
94
+ """
95
+ Search the list of entries for a match
96
+
97
+ :param s_attr: The attribute to search by
98
+ :param s_value: The value to search for
99
+ :param entries: List of entries to search
100
+ :return: First found match or None if not found
101
+ """
102
+ for s_entry in entries:
103
+ if getattr(s_entry, s_attr, None) == s_value:
104
+ return s_entry
105
+ return None
106
+
107
+ if not value:
108
+ return None
109
+ if collection:
110
+ # Collection was specified so check there
111
+ return (
112
+ search(attr, value, _collection.entries)
113
+ if (_collection := self._collections[slugify.slugify(collection)])
114
+ else None
115
+ )
116
+ if attr == "slug":
117
+ # We can handle slug a bit differently since it's the key to the route map dictionary.
118
+ if entry := self._route_map.get(value):
119
+ return entry
120
+ # Check the base route map
121
+ elif entry := search(attr, value, self._route_map.values()):
122
+ return entry
123
+ if full_search:
124
+ # Check each collection
125
+ for collection in self._collections.values():
126
+ if entry := search(attr, value, collection.entries):
127
+ return entry
128
+ return None
129
+
130
+ @property
131
+ def html(self) -> str:
132
+ """Build the site map as HTML"""
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.
136
+ for entry in self._route_map.values():
137
+ html_string += f'\t<li><a href="{urljoin(self.site_url, entry.url_for)}">{entry.title}</a></li>\n'
138
+ if entry.entries:
139
+ html_string += "\t<ul>\n"
140
+ html_string += "".join(
141
+ f'\t\t<li><a href="{urljoin(self.site_url, sub_entry.url_for)}">{sub_entry.title}</a></li>\n'
142
+ for sub_entry in entry.entries
143
+ )
144
+ html_string += "\t</ul>\n"
145
+ html_string += "</ul>\n"
146
+ return html_string
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: render_engine
3
- Version: 2025.9.1a1
3
+ Version: 2025.10.1a1
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/
@@ -40,7 +40,7 @@ Requires-Dist: watchfiles; extra == "dev"
40
40
 
41
41
  <!-- markdownlint-disable -->
42
42
  <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
43
- [![All Contributors](https://img.shields.io/badge/all_contributors-18-orange.svg?style=flat-square)](#contributors-)
43
+ [![All Contributors](https://img.shields.io/badge/all_contributors-20-orange.svg?style=flat-square)](#contributors-)
44
44
  <!-- ALL-CONTRIBUTORS-BADGE:END -->
45
45
  <!-- markdownlint-restore -->
46
46
 
@@ -100,22 +100,24 @@ We've compiled a set of [awesome add-ons](https://github.com/render-engine/rende
100
100
  <tr>
101
101
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/Tashuuuu"><img src="https://avatars.githubusercontent.com/u/85075827?v=4?s=57" width="57px;" alt="Akriti Sengar"/><br /><sub><b>Akriti Sengar</b></sub></a><br /><a href="#tool-Tashuuuu" title="Tools">🔧</a></td>
102
102
  <td align="center" valign="top" width="14.28%"><a href="https://tonybaloney.github.io/"><img src="https://avatars.githubusercontent.com/u/1532417?v=4?s=57" width="57px;" alt="Anthony Shaw"/><br /><sub><b>Anthony Shaw</b></sub></a><br /><a href="https://github.com/render-engine/render-engine/commits?author=tonybaloney" title="Documentation">📖</a></td>
103
+ <td align="center" valign="top" width="14.28%"><a href="https://b.dougie.dev/"><img src="https://avatars.githubusercontent.com/u/5713670?v=4?s=57" width="57px;" alt="Brian Douglas"/><br /><sub><b>Brian Douglas</b></sub></a><br /><a href="#financial-bdougie" title="Financial">💵</a></td>
103
104
  <td align="center" valign="top" width="14.28%"><a href="https://brassnet.biz"><img src="https://avatars.githubusercontent.com/u/1806946?v=4?s=57" width="57px;" alt="Dan Shernicoff"/><br /><sub><b>Dan Shernicoff</b></sub></a><br /><a href="https://github.com/render-engine/render-engine/commits?author=brass75" title="Code">💻</a></td>
104
105
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/mannyanebi"><img src="https://avatars.githubusercontent.com/u/25439000?v=4?s=57" width="57px;" alt="Emmanuel Anebi"/><br /><sub><b>Emmanuel Anebi</b></sub></a><br /><a href="#plugin-mannyanebi" title="Plugin/utility libraries">🔌</a></td>
105
106
  <td align="center" valign="top" width="14.28%"><a href="http://giovannimartins.dev"><img src="https://avatars.githubusercontent.com/u/7796661?v=4?s=57" width="57px;" alt="Giovanni Martins"/><br /><sub><b>Giovanni Martins</b></sub></a><br /><a href="#infra-giovannism20" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
106
- <td align="center" valign="top" width="14.28%"><a href="https://webology.dev"><img src="https://avatars.githubusercontent.com/u/50527?v=4?s=57" width="57px;" alt="Jeff Triplett"/><br /><sub><b>Jeff Triplett</b></sub></a><br /><a href="https://github.com/render-engine/render-engine/commits?author=jefftriplett" title="Documentation">📖</a> <a href="#mentoring-jefftriplett" title="Mentoring">🧑‍🏫</a></td>
107
- <td align="center" valign="top" width="14.28%"><a href="https://linktr.ee/john0isaac"><img src="https://avatars.githubusercontent.com/u/64026625?v=4?s=57" width="57px;" alt="John Aziz"/><br /><sub><b>John Aziz</b></sub></a><br /><a href="https://github.com/render-engine/render-engine/commits?author=john0isaac" title="Documentation">📖</a> <a href="#maintenance-john0isaac" title="Maintenance">🚧</a> <a href="#userTesting-john0isaac" title="User Testing">📓</a></td>
107
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/kjaymiller"><img src="https://avatars.githubusercontent.com/u/8632637?v=4?s=57" width="57px;" alt="Jay Miller"/><br /><sub><b>Jay Miller</b></sub></a><br /><a href="https://github.com/render-engine/render-engine/commits?author=kjaymiller" title="Code">💻</a> <a href="https://github.com/render-engine/render-engine/commits?author=kjaymiller" title="Documentation">📖</a> <a href="#maintenance-kjaymiller" title="Maintenance">🚧</a></td>
108
108
  </tr>
109
109
  <tr>
110
+ <td align="center" valign="top" width="14.28%"><a href="https://webology.dev"><img src="https://avatars.githubusercontent.com/u/50527?v=4?s=57" width="57px;" alt="Jeff Triplett"/><br /><sub><b>Jeff Triplett</b></sub></a><br /><a href="https://github.com/render-engine/render-engine/commits?author=jefftriplett" title="Documentation">📖</a> <a href="#mentoring-jefftriplett" title="Mentoring">🧑‍🏫</a></td>
111
+ <td align="center" valign="top" width="14.28%"><a href="https://linktr.ee/john0isaac"><img src="https://avatars.githubusercontent.com/u/64026625?v=4?s=57" width="57px;" alt="John Aziz"/><br /><sub><b>John Aziz</b></sub></a><br /><a href="https://github.com/render-engine/render-engine/commits?author=john0isaac" title="Documentation">📖</a> <a href="#maintenance-john0isaac" title="Maintenance">🚧</a> <a href="#userTesting-john0isaac" title="User Testing">📓</a></td>
110
112
  <td align="center" valign="top" width="14.28%"><a href="https://www.jonafato.com"><img src="https://avatars.githubusercontent.com/u/392720?v=4?s=57" width="57px;" alt="Jon Banafato"/><br /><sub><b>Jon Banafato</b></sub></a><br /><a href="https://github.com/render-engine/render-engine/commits?author=jonafato" title="Code">💻</a></td>
111
113
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/jlgimeno"><img src="https://avatars.githubusercontent.com/u/17421585?v=4?s=57" width="57px;" alt="Jorge L. Gimeno"/><br /><sub><b>Jorge L. Gimeno</b></sub></a><br /><a href="https://github.com/render-engine/render-engine/commits?author=jlgimeno" title="Documentation">📖</a></td>
112
114
  <td align="center" valign="top" width="14.28%"><a href="http://lauralangdon.io"><img src="https://avatars.githubusercontent.com/u/48335772?v=4?s=57" width="57px;" alt="Laura Langdon"/><br /><sub><b>Laura Langdon</b></sub></a><br /><a href="https://github.com/render-engine/render-engine/commits?author=LauraLangdon" title="Documentation">📖</a></td>
113
115
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/Mahhheshh"><img src="https://avatars.githubusercontent.com/u/100200105?v=4?s=57" width="57px;" alt="Mahhheshh"/><br /><sub><b>Mahhheshh</b></sub></a><br /><a href="https://github.com/render-engine/render-engine/commits?author=Mahhheshh" title="Documentation">📖</a> <a href="https://github.com/render-engine/render-engine/commits?author=Mahhheshh" title="Code">💻</a> <a href="#maintenance-Mahhheshh" title="Maintenance">🚧</a></td>
114
116
  <td align="center" valign="top" width="14.28%"><a href="https://www.pythonbynight.com"><img src="https://avatars.githubusercontent.com/u/46942991?v=4?s=57" width="57px;" alt="Mario Munoz"/><br /><sub><b>Mario Munoz</b></sub></a><br /><a href="https://github.com/render-engine/render-engine/commits?author=tataraba" title="Code">💻</a></td>
115
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/MohitKambli"><img src="https://avatars.githubusercontent.com/u/31406633?v=4?s=57" width="57px;" alt="Mohit Kambli"/><br /><sub><b>Mohit Kambli</b></sub></a><br /><a href="#infra-MohitKambli" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
116
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/OsamaRab3"><img src="https://avatars.githubusercontent.com/u/159753803?v=4?s=57" width="57px;" alt="Osama Rabea "/><br /><sub><b>Osama Rabea </b></sub></a><br /><a href="https://github.com/render-engine/render-engine/commits?author=OsamaRab3" title="Documentation">📖</a> <a href="https://github.com/render-engine/render-engine/commits?author=OsamaRab3" title="Tests">⚠️</a></td>
117
117
  </tr>
118
118
  <tr>
119
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/MohitKambli"><img src="https://avatars.githubusercontent.com/u/31406633?v=4?s=57" width="57px;" alt="Mohit Kambli"/><br /><sub><b>Mohit Kambli</b></sub></a><br /><a href="#infra-MohitKambli" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
120
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/OsamaRab3"><img src="https://avatars.githubusercontent.com/u/159753803?v=4?s=57" width="57px;" alt="Osama Rabea "/><br /><sub><b>Osama Rabea </b></sub></a><br /><a href="https://github.com/render-engine/render-engine/commits?author=OsamaRab3" title="Documentation">📖</a> <a href="https://github.com/render-engine/render-engine/commits?author=OsamaRab3" title="Tests">⚠️</a></td>
119
121
  <td align="center" valign="top" width="14.28%"><a href="https://www.pamelafox.org"><img src="https://avatars.githubusercontent.com/u/297042?v=4?s=57" width="57px;" alt="Pamela Fox"/><br /><sub><b>Pamela Fox</b></sub></a><br /><a href="https://github.com/render-engine/render-engine/commits?author=pamelafox" title="Documentation">📖</a></td>
120
122
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/viktoriussuwandi"><img src="https://avatars.githubusercontent.com/u/68414300?v=4?s=57" width="57px;" alt="Viktorius Suwandi"/><br /><sub><b>Viktorius Suwandi</b></sub></a><br /><a href="https://github.com/render-engine/render-engine/commits?author=viktoriussuwandi" title="Code">💻</a></td>
121
123
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/VinayakG311"><img src="https://avatars.githubusercontent.com/u/96966973?v=4?s=57" width="57px;" alt="VinayakG311"/><br /><sub><b>VinayakG311</b></sub></a><br /><a href="#plugin-VinayakG311" title="Plugin/utility libraries">🔌</a></td>
@@ -1,18 +1,19 @@
1
1
  render_engine/.gitignore,sha256=74oa8YR8gNxKxB6lCtoqmgtB2xpZnWM539Qsl4wI_lg,12
2
2
  render_engine/__init__.py,sha256=3fgua4ZA9o1pvQ5unhY1gRARLXFqAu019NEYqZTjP20,154
3
3
  render_engine/__main__.py,sha256=uI7aBBZz0qSDwwwD11nS5oltWsuLw9hStfYo8O1aNws,144
4
- render_engine/_base_object.py,sha256=gjwsQ8rC7xXaa9sum2nTfY9GWUkFWFqZcS5Btvg9C3I,3312
4
+ render_engine/_base_object.py,sha256=DIyLdQ6gS4a0DP46zwGYkYIyewh8uRFznJmaUch7d8M,3546
5
5
  render_engine/archive.py,sha256=S3-kCmDNVKkEfKDKxcEk-sXkBD0vS0RDnFfPunYkU8g,2072
6
6
  render_engine/blog.py,sha256=f9GqFUFsta0KZnFhCiajobpfQyALqvgI5sbLm6zt1zw,1571
7
- render_engine/collection.py,sha256=GGCFTfMTEZuTuPZNg8vTap9JoDZVoRtR17RcsqPlyL8,9190
7
+ render_engine/collection.py,sha256=rubgKV6z3LN1gCGdYsbwd023SVQdBNnSHnHqXhkn83g,10209
8
8
  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=f9vnQ0ypHgbEh0MyIzp5VkJRhz0RLIOYZOX6OnmDlmA,6617
12
+ render_engine/page.py,sha256=mIKdOmyQ1zRcaoZej9hMV2xajH_frXllaO-ZpZMzCtM,8091
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=yOVjtZkdtaRNGicD8KDBdOjcPVUt1WN7Qa6x8-De5F8,13375
15
+ render_engine/site.py,sha256=URvG7OQSgeFOcrZwrD8vHLUWa6UxDBxIP-UMeLLqhZM,12936
16
+ render_engine/site_map.py,sha256=cVnCekhDqPKXCSslI8eg-_4cRHN_yWuWo82sjwKzAGI,5412
16
17
  render_engine/themes.py,sha256=TFG1rd34QCBvBWfeDbawgsn6kprmjsDTa1pdDSwDMic,4207
17
18
  render_engine/extras/__init__.py,sha256=L4jr4A7Jl-ODnSx1q2fP3_dBo37Dw6yepNRddu1nFNo,72
18
19
  render_engine/parsers/markdown.py,sha256=0jpixCaoHaL0IRSvFIljJIRCvFkXoKTEYQNK38LwMDU,287
@@ -23,15 +24,15 @@ render_engine/render_engine_templates/base_collection_path.md,sha256=7PIn5Oxei-b
23
24
  render_engine/render_engine_templates/page.html,sha256=p9aZ6_6mouPT1c8FVg-f14MeSt4OB-sH3hcXTY6jGmA,42
24
25
  render_engine/render_engine_templates/rss2.0.xml,sha256=Z5LGUdPRr3fQhXtJCjR8uxiPQ_GqYHELleDaS8Tr1oU,1803
25
26
  render_engine/render_engine_templates/rss2.0_items.xml,sha256=jxhDG9K2R112-upqq4NZnahDGZcvlWIn_sl9b4ArVEI,1306
26
- render_engine/render_engine_templates/sitemap.xml,sha256=A4zhyx2Z9RLTwTVyvK5kKpnXGRMJ4xwkf_ueI05Z5Hs,180
27
- 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
28
29
  render_engine/render_engine_templates/base_templates/_archive.html,sha256=eFd62LkMcdxz70g-ksmCmTr2TSnKt2Z2X8zx80JpAPk,189
29
30
  render_engine/render_engine_templates/base_templates/_base.html,sha256=piaDrWjE1gG0IvUzpaH1RmHsTeRheeG3S9YmTdIf-CI,710
30
31
  render_engine/render_engine_templates/base_templates/_page.html,sha256=jjrY2BAwlJeVSNF4Pa6rd4Kcg0dfYpPpieZE2zeMTOM,163
31
32
  render_engine/render_engine_templates/components/footer.html,sha256=HkPGGhfN0HcYm7t8zgXWCQ3bsCbT8FxT4_n2-9e1zUE,74
32
33
  render_engine/render_engine_templates/components/page_title.html,sha256=l8aE1TY94UPHXHqAyy6jv4IoN2Hv9cbrTPh7ILkMyxg,137
33
34
  render_engine/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
- render_engine-2025.9.1a1.dist-info/METADATA,sha256=wkAhLBfBNeo23vNfk5B7vMciUCBikz0pVqbmtMo8QH8,11074
35
- render_engine-2025.9.1a1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
36
- render_engine-2025.9.1a1.dist-info/top_level.txt,sha256=aNGALDMsFyrusho04AvUjSivsgEE9tQp_LP_jGr312Q,14
37
- render_engine-2025.9.1a1.dist-info/RECORD,,
35
+ render_engine-2025.10.1a1.dist-info/METADATA,sha256=X7uol4Ld4scOzJSZ0jYobZmf_AyTwJ53DAwt1hM488s,11895
36
+ render_engine-2025.10.1a1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
+ render_engine-2025.10.1a1.dist-info/top_level.txt,sha256=aNGALDMsFyrusho04AvUjSivsgEE9tQp_LP_jGr312Q,14
38
+ render_engine-2025.10.1a1.dist-info/RECORD,,