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.
- render_engine/_base_object.py +5 -0
- render_engine/collection.py +27 -0
- render_engine/page.py +34 -1
- render_engine/render_engine_templates/sitemap.xml +2 -1
- render_engine/render_engine_templates/sitemap_item.xml +2 -2
- render_engine/site.py +54 -61
- render_engine/site_map.py +146 -0
- {render_engine-2025.9.1a1.dist-info → render_engine-2025.10.1a1.dist-info}/METADATA +8 -6
- {render_engine-2025.9.1a1.dist-info → render_engine-2025.10.1a1.dist-info}/RECORD +11 -10
- {render_engine-2025.9.1a1.dist-info → render_engine-2025.10.1a1.dist-info}/WHEEL +0 -0
- {render_engine-2025.9.1a1.dist-info → render_engine-2025.10.1a1.dist-info}/top_level.txt +0 -0
render_engine/_base_object.py
CHANGED
|
@@ -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.')
|
render_engine/collection.py
CHANGED
|
@@ -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
|
-
**
|
|
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,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
|
-
<
|
|
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
|
-
|
|
307
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
[](#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://
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
27
|
-
render_engine/render_engine_templates/sitemap_item.xml,sha256=
|
|
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.
|
|
35
|
-
render_engine-2025.
|
|
36
|
-
render_engine-2025.
|
|
37
|
-
render_engine-2025.
|
|
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,,
|
|
File without changes
|
|
File without changes
|