render-engine 2026.1.1a2__py3-none-any.whl → 2026.2.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/__version__.py +2 -2
- render_engine/collection.py +21 -8
- render_engine/content_managers/base_content_manager.py +15 -1
- render_engine/content_managers/file_content_manager.py +6 -9
- render_engine/engine.py +21 -12
- render_engine/feeds.py +7 -0
- render_engine/page.py +12 -7
- render_engine/plugins.py +1 -1
- render_engine/render_engine_templates/base_collection_path.md +1 -1
- render_engine/site.py +9 -15
- render_engine/site_map.py +3 -2
- render_engine/themes.py +24 -6
- {render_engine-2026.1.1a2.dist-info → render_engine-2026.2.1a1.dist-info}/METADATA +23 -30
- {render_engine-2026.1.1a2.dist-info → render_engine-2026.2.1a1.dist-info}/RECORD +16 -18
- {render_engine-2026.1.1a2.dist-info → render_engine-2026.2.1a1.dist-info}/WHEEL +1 -1
- render_engine/__main__.py +0 -6
- render_engine/extras/__init__.py +0 -3
- {render_engine-2026.1.1a2.dist-info → render_engine-2026.2.1a1.dist-info}/top_level.txt +0 -0
render_engine/__version__.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '2026.
|
|
32
|
-
__version_tuple__ = version_tuple = (2026,
|
|
31
|
+
__version__ = version = '2026.2.1a1'
|
|
32
|
+
__version_tuple__ = version_tuple = (2026, 2, 1, 'a1')
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
render_engine/collection.py
CHANGED
|
@@ -2,10 +2,10 @@ import copy
|
|
|
2
2
|
import datetime
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
|
-
from collections.abc import Callable, Generator
|
|
5
|
+
from collections.abc import Callable, Generator, Iterable
|
|
6
6
|
from multiprocessing.pool import ThreadPool
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Any
|
|
8
|
+
from typing import Any, cast
|
|
9
9
|
|
|
10
10
|
import dateutil.parser as dateparse
|
|
11
11
|
from more_itertools import batched
|
|
@@ -81,16 +81,17 @@ class Collection(BaseObject):
|
|
|
81
81
|
feed_title: str
|
|
82
82
|
include_suffixes: list[str] = ["*.md", "*.html"]
|
|
83
83
|
items_per_page: int | None
|
|
84
|
-
Parser: BasePageParser = BasePageParser
|
|
84
|
+
Parser: type[BasePageParser] = BasePageParser
|
|
85
85
|
parser_extras: dict[str, Any]
|
|
86
86
|
required_themes: list[Callable]
|
|
87
87
|
routes: list[str | Path] = ["./"]
|
|
88
|
+
site = None
|
|
88
89
|
sort_by: str | list = "_title"
|
|
89
90
|
sort_reverse: bool = False
|
|
90
91
|
template_vars: dict[str, Any]
|
|
91
92
|
template: str | None
|
|
92
93
|
plugin_manager: PluginManager | None
|
|
93
|
-
ContentManager: type[ContentManager]
|
|
94
|
+
ContentManager: type[ContentManager] = FileContentManager
|
|
94
95
|
content_manager_extras: dict[str, Any]
|
|
95
96
|
|
|
96
97
|
def __init__(
|
|
@@ -119,7 +120,7 @@ class Collection(BaseObject):
|
|
|
119
120
|
cm_extras.update(self.content_manager_extras)
|
|
120
121
|
self.content_manager = self.ContentManager(**cm_extras)
|
|
121
122
|
if hasattr(self, "pages"):
|
|
122
|
-
self.content_manager.pages = self.pages
|
|
123
|
+
self.content_manager.pages = cast(Iterable, self.pages)
|
|
123
124
|
|
|
124
125
|
def get_page(
|
|
125
126
|
self,
|
|
@@ -275,6 +276,7 @@ class Collection(BaseObject):
|
|
|
275
276
|
:param site: The site object triggering the call
|
|
276
277
|
:param hook_type: The hook to run
|
|
277
278
|
"""
|
|
279
|
+
self.plugin_manager = cast(PluginManager, self.plugin_manager)
|
|
278
280
|
if not getattr(self.plugin_manager, "_pm", None) or not self.plugin_manager.plugins:
|
|
279
281
|
return
|
|
280
282
|
try:
|
|
@@ -284,15 +286,22 @@ class Collection(BaseObject):
|
|
|
284
286
|
return
|
|
285
287
|
method(collection=self, site=site, settings=self.plugin_manager.plugin_settings)
|
|
286
288
|
|
|
287
|
-
def _render(self, entry):
|
|
289
|
+
def _render(self, entry: BaseObject):
|
|
288
290
|
"""
|
|
289
291
|
Renders 1 entry in the Collection
|
|
290
292
|
|
|
291
293
|
:param entry: The entry to process
|
|
292
294
|
"""
|
|
293
295
|
if not isinstance(entry, RSSFeed) and not isinstance(entry, Archive):
|
|
294
|
-
entry.plugin_manager = copy.deepcopy(self.plugin_manager)
|
|
296
|
+
entry.plugin_manager: PluginManager = copy.deepcopy(self.plugin_manager)
|
|
295
297
|
|
|
298
|
+
# Circular imports. Need to be handled here.
|
|
299
|
+
from .page import BasePage
|
|
300
|
+
from .site import Site
|
|
301
|
+
|
|
302
|
+
entry = cast(BasePage, entry)
|
|
303
|
+
self = cast(Collection, self)
|
|
304
|
+
self.site = cast(Site, self.site)
|
|
296
305
|
entry.site = self.site
|
|
297
306
|
entry.render(self.site.theme_manager)
|
|
298
307
|
|
|
@@ -308,7 +317,11 @@ class Collection(BaseObject):
|
|
|
308
317
|
pass
|
|
309
318
|
|
|
310
319
|
def create_entry(
|
|
311
|
-
self,
|
|
320
|
+
self,
|
|
321
|
+
filepath: Path | None = None,
|
|
322
|
+
editor: str | None = None,
|
|
323
|
+
content: str | None = None,
|
|
324
|
+
metadata: dict | None = None,
|
|
312
325
|
) -> str:
|
|
313
326
|
"""
|
|
314
327
|
Create a new entry for the Collection
|
|
@@ -12,12 +12,26 @@ class ContentManager(ABC):
|
|
|
12
12
|
"""The Page objects managed by the content manager"""
|
|
13
13
|
...
|
|
14
14
|
|
|
15
|
+
@pages.setter
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def pages(self, value: Iterable):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
def __len__(self):
|
|
21
|
+
return len(list(self.pages))
|
|
22
|
+
|
|
15
23
|
def __iter__(self) -> Generator:
|
|
16
24
|
"""Iterator for the ContentManager"""
|
|
17
25
|
yield from self.pages
|
|
18
26
|
|
|
19
27
|
@abstractmethod
|
|
20
|
-
def create_entry(
|
|
28
|
+
def create_entry(
|
|
29
|
+
self,
|
|
30
|
+
filepath: Path | None = None,
|
|
31
|
+
editor: str | None = None,
|
|
32
|
+
metadata: dict | None = None,
|
|
33
|
+
content: str | None = None,
|
|
34
|
+
):
|
|
21
35
|
"""Create a new entry"""
|
|
22
36
|
pass
|
|
23
37
|
|
|
@@ -36,15 +36,12 @@ class FileContentManager(ContentManager):
|
|
|
36
36
|
def pages(self, value: Iterable):
|
|
37
37
|
self._pages = value
|
|
38
38
|
|
|
39
|
-
def __len__(self):
|
|
40
|
-
return len(list(self.pages))
|
|
41
|
-
|
|
42
39
|
def create_entry(
|
|
43
40
|
self,
|
|
44
|
-
filepath: Path = None,
|
|
45
|
-
editor: str = None,
|
|
46
|
-
metadata: dict = None,
|
|
47
|
-
content: str = None,
|
|
41
|
+
filepath: Path | None = None,
|
|
42
|
+
editor: str | None = None,
|
|
43
|
+
metadata: dict | None = None,
|
|
44
|
+
content: str | None = None,
|
|
48
45
|
update: bool = False,
|
|
49
46
|
) -> str:
|
|
50
47
|
"""
|
|
@@ -62,13 +59,13 @@ class FileContentManager(ContentManager):
|
|
|
62
59
|
if not update and filepath.exists():
|
|
63
60
|
raise RuntimeError(f"File at {filepath} exists and update is disabled.")
|
|
64
61
|
|
|
65
|
-
parsed_content = self.collection.Parser.create_entry(content=content, **metadata)
|
|
62
|
+
parsed_content = self.collection.Parser.create_entry(content=content, **(metadata or {}))
|
|
66
63
|
filepath.write_text(parsed_content)
|
|
67
64
|
if editor:
|
|
68
65
|
subprocess.run([editor, filepath])
|
|
69
66
|
return f"New entry created at {filepath} ."
|
|
70
67
|
|
|
71
|
-
def update_entry(self, page, *, content: str = None, **kwargs) -> str:
|
|
68
|
+
def update_entry(self, page, *, content: str | None = None, **kwargs) -> str:
|
|
72
69
|
"""
|
|
73
70
|
Update an entry
|
|
74
71
|
|
render_engine/engine.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
from email.utils import format_datetime as fmt_datetime
|
|
3
|
+
from typing import cast
|
|
3
4
|
from urllib.parse import urljoin
|
|
4
5
|
|
|
5
6
|
from dateutil.parser import parse
|
|
@@ -13,7 +14,9 @@ from jinja2 import (
|
|
|
13
14
|
select_autoescape,
|
|
14
15
|
)
|
|
15
16
|
|
|
17
|
+
from ._base_object import BaseObject
|
|
16
18
|
from .collection import Collection
|
|
19
|
+
from .page import BasePage
|
|
17
20
|
|
|
18
21
|
render_engine_templates_loader = ChoiceLoader(
|
|
19
22
|
[
|
|
@@ -56,10 +59,11 @@ def format_datetime(
|
|
|
56
59
|
datetime_format: str | None = None,
|
|
57
60
|
) -> str:
|
|
58
61
|
"""Parse information from the given class object."""
|
|
62
|
+
format: str
|
|
59
63
|
if datetime_format:
|
|
60
64
|
format = datetime_format
|
|
61
65
|
else:
|
|
62
|
-
format = env.globals.get("DATETIME_FORMAT", "%Y-%m-%d")
|
|
66
|
+
format = cast(str, env.globals.get("DATETIME_FORMAT", "%Y-%m-%d"))
|
|
63
67
|
|
|
64
68
|
return value.strftime(format)
|
|
65
69
|
|
|
@@ -69,7 +73,8 @@ engine.filters["format_datetime"] = format_datetime
|
|
|
69
73
|
|
|
70
74
|
@pass_environment
|
|
71
75
|
def to_absolute(env: Environment, url: str) -> str:
|
|
72
|
-
|
|
76
|
+
site_url: str = cast(str, env.globals.get("SITE_URL"))
|
|
77
|
+
return str(urljoin(site_url, url))
|
|
73
78
|
|
|
74
79
|
|
|
75
80
|
engine.filters["to_absolute"] = to_absolute
|
|
@@ -78,10 +83,11 @@ engine.filters["to_absolute"] = to_absolute
|
|
|
78
83
|
@pass_environment
|
|
79
84
|
def feed_url(env: Environment, value: str) -> str:
|
|
80
85
|
"""Returns the URL for the collections feed"""
|
|
81
|
-
routes = env.globals.get("routes")
|
|
86
|
+
routes = cast(dict[str, BaseObject], env.globals.get("routes"))
|
|
82
87
|
|
|
83
88
|
if routes:
|
|
84
|
-
|
|
89
|
+
route = cast(Collection, routes[value])
|
|
90
|
+
return route.feed.url_for()
|
|
85
91
|
|
|
86
92
|
else:
|
|
87
93
|
raise ValueError("No Route Found")
|
|
@@ -93,21 +99,24 @@ engine.filters["feed_url"] = feed_url
|
|
|
93
99
|
@pass_environment
|
|
94
100
|
def url_for(env: Environment, value: str, page: int = 0) -> str:
|
|
95
101
|
"""Look for the route in the route_list and return the url for the page."""
|
|
96
|
-
routes = env.globals.get("routes")
|
|
97
|
-
|
|
102
|
+
routes = cast(dict[str, BaseObject], env.globals.get("routes"))
|
|
103
|
+
|
|
104
|
+
if "." in value:
|
|
105
|
+
collection, route = value.split(".", maxsplit=1)
|
|
98
106
|
|
|
99
|
-
if len(route) == 2 and isinstance(route, list):
|
|
100
|
-
collection, route = route
|
|
101
107
|
if collection := routes.get(collection, None):
|
|
108
|
+
collection = cast(Collection, collection)
|
|
102
109
|
for page in collection:
|
|
103
110
|
if getattr(page, page._reference) == route:
|
|
104
111
|
return page.url_for()
|
|
105
112
|
|
|
106
113
|
else:
|
|
107
|
-
route
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
route: BaseObject | None
|
|
115
|
+
match route := routes.get(value):
|
|
116
|
+
case Collection():
|
|
117
|
+
return list(route.archives)[page].url_for()
|
|
118
|
+
case BasePage():
|
|
119
|
+
return route.url_for()
|
|
111
120
|
|
|
112
121
|
raise ValueError(f"{value} is not a valid route.")
|
|
113
122
|
|
render_engine/feeds.py
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Feed Objects for Generating RSS Feeds
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from render_engine_parser import BasePageParser
|
|
6
|
+
|
|
5
7
|
from .page import BasePage
|
|
6
8
|
|
|
7
9
|
|
|
@@ -35,3 +37,8 @@ class RSSFeed(BasePage):
|
|
|
35
37
|
|
|
36
38
|
template = "rss2.0.xml"
|
|
37
39
|
extension: str = ".rss"
|
|
40
|
+
Parser: type[BasePageParser] = BasePageParser
|
|
41
|
+
|
|
42
|
+
def __init__(self):
|
|
43
|
+
self.pages: list = list()
|
|
44
|
+
self.slug: str = self._slug or self.__class__.__name__
|
render_engine/page.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import re
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Any
|
|
4
|
+
from typing import Any, cast
|
|
5
5
|
|
|
6
6
|
from jinja2 import Environment, Template
|
|
7
7
|
from render_engine_parser.base_parsers import BasePageParser
|
|
@@ -43,14 +43,15 @@ class BasePage(BaseObject):
|
|
|
43
43
|
plugin_manager: PluginManager | None
|
|
44
44
|
site = None # This is a Site but circular imports so we can't actually type hint it.
|
|
45
45
|
no_prerender: bool = False
|
|
46
|
+
collection: dict | None = None
|
|
46
47
|
|
|
47
48
|
@property
|
|
48
|
-
def _content(self) ->
|
|
49
|
+
def _content(self) -> Any:
|
|
49
50
|
"""returns the content of the page."""
|
|
50
51
|
return getattr(self, "content", None)
|
|
51
52
|
|
|
52
53
|
@property
|
|
53
|
-
def _data(self) ->
|
|
54
|
+
def _data(self) -> Any:
|
|
54
55
|
"""returns the content of the page."""
|
|
55
56
|
return getattr(self, "data", None)
|
|
56
57
|
|
|
@@ -152,12 +153,16 @@ class BasePage(BaseObject):
|
|
|
152
153
|
def render(self, theme_manager: ThemeManager) -> int:
|
|
153
154
|
"""Render the page to the file system"""
|
|
154
155
|
rc = 0
|
|
156
|
+
from .site import Site
|
|
157
|
+
|
|
158
|
+
site: Site = cast(Site, self.site)
|
|
159
|
+
|
|
155
160
|
for route in self.routes:
|
|
156
|
-
path = Path(
|
|
161
|
+
path = Path(site.output_path) / Path(route) / Path(self.path_name)
|
|
157
162
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
158
163
|
settings = dict()
|
|
159
164
|
if (pm := getattr(self, "plugin_manager", None)) and pm is not None:
|
|
160
|
-
settings = {**
|
|
165
|
+
settings = {**site.plugin_manager.plugin_settings, "route": route}
|
|
161
166
|
pm.hook.render_content(page=self, settings=settings, site=self.site)
|
|
162
167
|
self.rendered_content = self._render_content(theme_manager.engine)
|
|
163
168
|
# pass the route to the plugin settings
|
|
@@ -228,11 +233,11 @@ class Page(BasePage):
|
|
|
228
233
|
Defaults to `BasePageParser`.
|
|
229
234
|
"""
|
|
230
235
|
if Parser:
|
|
231
|
-
self.Parser = Parser
|
|
236
|
+
self.Parser = cast(type[BasePageParser], Parser)
|
|
232
237
|
|
|
233
238
|
# Parse Content from the Content Path or the Content
|
|
234
239
|
if content_path := (content_path or getattr(self, "content_path", None)):
|
|
235
|
-
self.metadata, self.content = self.Parser.parse_content_path(content_path)
|
|
240
|
+
self.metadata, self.content = self.Parser.parse_content_path(str(content_path))
|
|
236
241
|
|
|
237
242
|
elif content := (content or getattr(self, "content", None)):
|
|
238
243
|
self.metadata, self.content = self.Parser.parse_content(content)
|
render_engine/plugins.py
CHANGED
render_engine/site.py
CHANGED
|
@@ -65,7 +65,7 @@ class Site:
|
|
|
65
65
|
def __init__(
|
|
66
66
|
self,
|
|
67
67
|
) -> None:
|
|
68
|
-
self.plugin_manager = PluginManager()
|
|
68
|
+
self.plugin_manager: PluginManager = PluginManager()
|
|
69
69
|
self.theme_manager = ThemeManager(
|
|
70
70
|
engine=engine,
|
|
71
71
|
output_path=self._output_path,
|
|
@@ -75,8 +75,7 @@ class Site:
|
|
|
75
75
|
self.site_settings: dict = {}
|
|
76
76
|
self.subcollections: dict[str, list] = {"pages": []}
|
|
77
77
|
self.theme_manager.engine.globals.update(self.site_vars)
|
|
78
|
-
|
|
79
|
-
self.theme_manager.engine.loader.loaders.insert(0, FileSystemLoader(self._template_path))
|
|
78
|
+
self.theme_manager.add_loader(0, FileSystemLoader(self._template_path))
|
|
80
79
|
self._site_map = None
|
|
81
80
|
|
|
82
81
|
@property
|
|
@@ -96,7 +95,7 @@ class Site:
|
|
|
96
95
|
self.theme_manager.static_paths = static_paths
|
|
97
96
|
|
|
98
97
|
@property
|
|
99
|
-
def site_map(self) -> SiteMap:
|
|
98
|
+
def site_map(self) -> SiteMap | None:
|
|
100
99
|
return self._site_map
|
|
101
100
|
|
|
102
101
|
def update_site_vars(self, **kwargs) -> None:
|
|
@@ -179,7 +178,7 @@ class Site:
|
|
|
179
178
|
self.route_list[_Collection._slug] = _Collection
|
|
180
179
|
return _Collection
|
|
181
180
|
|
|
182
|
-
def page(self, _page: Page) -> Page:
|
|
181
|
+
def page(self, _page: type[Page]) -> Page:
|
|
183
182
|
"""
|
|
184
183
|
Add the page to the route list to be rendered later.
|
|
185
184
|
Also remaps `title` in case the user wants to use it in the template rendering.
|
|
@@ -228,22 +227,17 @@ class Site:
|
|
|
228
227
|
# load themes in the ChoiceLoader/FileLoader
|
|
229
228
|
for theme_prefix, theme_loader in self.theme_manager.prefix.items():
|
|
230
229
|
logging.info(f"loading theme: {theme_prefix}")
|
|
231
|
-
|
|
232
|
-
self.theme_manager.engine.loader.loaders.insert(-1, theme_loader)
|
|
230
|
+
self.theme_manager.add_loader(-1, theme_loader)
|
|
233
231
|
# load themes in the PrefixLoader
|
|
234
|
-
|
|
235
|
-
self.theme_manager.engine.loader.loaders.insert(-1, PrefixLoader(self.theme_manager.prefix))
|
|
232
|
+
self.theme_manager.add_loader(-1, PrefixLoader(self.theme_manager.prefix))
|
|
236
233
|
|
|
237
234
|
@property
|
|
238
235
|
def template_path(self) -> str:
|
|
239
|
-
|
|
240
|
-
return self.theme_manager.engine.loader.loaders[0].searchpath[0]
|
|
241
|
-
return ""
|
|
236
|
+
return self.theme_manager.template_path
|
|
242
237
|
|
|
243
238
|
@template_path.setter
|
|
244
239
|
def template_path(self, template_path: str) -> None:
|
|
245
|
-
|
|
246
|
-
self.theme_manager.engine.loader.loaders.insert(0, FileSystemLoader(template_path))
|
|
240
|
+
self.theme_manager.add_loader(0, FileSystemLoader(template_path))
|
|
247
241
|
|
|
248
242
|
def render(self) -> None:
|
|
249
243
|
"""
|
|
@@ -287,7 +281,7 @@ class Site:
|
|
|
287
281
|
self.plugin_manager.hook.pre_build_site(
|
|
288
282
|
site=self,
|
|
289
283
|
settings=self.plugin_manager.plugin_settings,
|
|
290
|
-
)
|
|
284
|
+
)
|
|
291
285
|
|
|
292
286
|
self.load_themes()
|
|
293
287
|
self.theme_manager.engine.globals.update(self.site_vars)
|
render_engine/site_map.py
CHANGED
|
@@ -16,13 +16,14 @@ class SiteMapEntry:
|
|
|
16
16
|
self.slug = entry._slug
|
|
17
17
|
self.title = entry._title
|
|
18
18
|
self.path_name = entry.path_name
|
|
19
|
+
route = str(route)
|
|
19
20
|
match entry:
|
|
20
21
|
case Page():
|
|
21
22
|
# For a base page the _route created if we use the route is invalid - just use the path_name
|
|
22
23
|
self._route = f"/{route.lstrip('/')}/{self.path_name}" if from_collection else f"/{self.path_name}"
|
|
23
24
|
self.entries = list()
|
|
24
25
|
case Collection():
|
|
25
|
-
self._route = f"/{entry.routes[0].lstrip('/')}"
|
|
26
|
+
self._route = f"/{str(entry.routes[0]).lstrip('/')}"
|
|
26
27
|
self.entries = [
|
|
27
28
|
SiteMapEntry(collection_entry, self._route, from_collection=True) for collection_entry in entry
|
|
28
29
|
]
|
|
@@ -72,7 +73,7 @@ class SiteMap:
|
|
|
72
73
|
self,
|
|
73
74
|
value: str,
|
|
74
75
|
attr: str = "slug",
|
|
75
|
-
collection: str = None,
|
|
76
|
+
collection: str | None = None,
|
|
76
77
|
full_search: bool = False,
|
|
77
78
|
) -> SiteMapEntry | None:
|
|
78
79
|
"""
|
render_engine/themes.py
CHANGED
|
@@ -3,9 +3,10 @@ import logging
|
|
|
3
3
|
import pathlib
|
|
4
4
|
import shutil
|
|
5
5
|
from pathlib import Path
|
|
6
|
+
from typing import cast
|
|
6
7
|
|
|
7
8
|
import slugify
|
|
8
|
-
from jinja2 import BaseLoader, Environment
|
|
9
|
+
from jinja2 import BaseLoader, ChoiceLoader, Environment, FileSystemLoader
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
@dataclasses.dataclass
|
|
@@ -95,11 +96,15 @@ class ThemeManager:
|
|
|
95
96
|
if theme.template_globals:
|
|
96
97
|
for key, value in theme.template_globals.items():
|
|
97
98
|
if isinstance(value, set) and isinstance(self.engine.globals.get(key), set):
|
|
98
|
-
self.engine.globals.
|
|
99
|
-
|
|
100
|
-
self.engine.globals[key]
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
entry: set = cast(set, self.engine.globals.get(key, set()))
|
|
100
|
+
entry.update(value)
|
|
101
|
+
self.engine.globals[key] = entry
|
|
102
|
+
match self.engine.globals.get(key):
|
|
103
|
+
case set():
|
|
104
|
+
entry: set = cast(set, self.engine.globals[key])
|
|
105
|
+
entry.add(value)
|
|
106
|
+
case _:
|
|
107
|
+
self.engine.globals[key] = value
|
|
103
108
|
|
|
104
109
|
def _render_static(self) -> None:
|
|
105
110
|
"""Copies a Static Directory to the output folder"""
|
|
@@ -111,3 +116,16 @@ class ThemeManager:
|
|
|
111
116
|
pathlib.Path(self.output_path) / pathlib.Path(static_path).name,
|
|
112
117
|
dirs_exist_ok=True,
|
|
113
118
|
)
|
|
119
|
+
|
|
120
|
+
def add_loader(self, idx: int, loader: BaseLoader):
|
|
121
|
+
"""Add a loader to the list of loaders"""
|
|
122
|
+
if self.engine.loader is not None and isinstance(self.engine.loader, ChoiceLoader):
|
|
123
|
+
loaders: list[BaseLoader] = cast(list[BaseLoader], self.engine.loader.loaders)
|
|
124
|
+
loaders.insert(idx, loader)
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def template_path(self) -> str:
|
|
128
|
+
if self.engine.loader is not None and isinstance(self.engine.loader, ChoiceLoader):
|
|
129
|
+
loader: FileSystemLoader = cast(FileSystemLoader, self.engine.loader.loaders[0])
|
|
130
|
+
return loader.searchpath[0]
|
|
131
|
+
return ""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: render_engine
|
|
3
|
-
Version: 2026.
|
|
3
|
+
Version: 2026.2.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/
|
|
@@ -21,22 +21,6 @@ Provides-Extra: cli
|
|
|
21
21
|
Requires-Dist: render-engine-cli; extra == "cli"
|
|
22
22
|
Provides-Extra: extras
|
|
23
23
|
Requires-Dist: render-engine-sitemap; extra == "extras"
|
|
24
|
-
Provides-Extra: dev
|
|
25
|
-
Requires-Dist: cookiecutter; extra == "dev"
|
|
26
|
-
Requires-Dist: ephemeral-port-reserve; extra == "dev"
|
|
27
|
-
Requires-Dist: httpx; extra == "dev"
|
|
28
|
-
Requires-Dist: mkdocs; extra == "dev"
|
|
29
|
-
Requires-Dist: mkdocs-material; extra == "dev"
|
|
30
|
-
Requires-Dist: mkdocstrings[python]; extra == "dev"
|
|
31
|
-
Requires-Dist: mypy; extra == "dev"
|
|
32
|
-
Requires-Dist: pre-commit; extra == "dev"
|
|
33
|
-
Requires-Dist: pymdown-extensions; extra == "dev"
|
|
34
|
-
Requires-Dist: pytest; extra == "dev"
|
|
35
|
-
Requires-Dist: pytest-cov; extra == "dev"
|
|
36
|
-
Requires-Dist: pytest-mock; extra == "dev"
|
|
37
|
-
Requires-Dist: ruff; extra == "dev"
|
|
38
|
-
Requires-Dist: toml; extra == "dev"
|
|
39
|
-
Requires-Dist: watchfiles; extra == "dev"
|
|
40
24
|
|
|
41
25
|
<!-- markdownlint-disable -->
|
|
42
26
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
|
@@ -47,6 +31,7 @@ Requires-Dist: watchfiles; extra == "dev"
|
|
|
47
31
|
# Render Engine
|
|
48
32
|
|
|
49
33
|
[](https://github.com/kjaymiller/render_engine/actions/workflows/test.yml)
|
|
34
|
+

|
|
50
35
|
[](https://discord.gg/2xMQ4j4d8m)
|
|
51
36
|
|
|
52
37
|
## Learn More
|
|
@@ -59,9 +44,12 @@ Requires-Dist: watchfiles; extra == "dev"
|
|
|
59
44
|
|
|
60
45
|
## The _3 layer_ Architecture
|
|
61
46
|
|
|
62
|
-
- **[Page]
|
|
63
|
-
|
|
64
|
-
- **[
|
|
47
|
+
- **[Page][page-docs]** - A single webpage item built from content, a template,
|
|
48
|
+
raw data, or a combination of those things.
|
|
49
|
+
- **[Collection][collection-docs]** - A group of webpages built from the same
|
|
50
|
+
template, organized in a single directory
|
|
51
|
+
- **[Site][site-docs]** - The container that helps to render all Pages and
|
|
52
|
+
Collections with uniform settings and variables
|
|
65
53
|
|
|
66
54
|
## Installing Render Engine
|
|
67
55
|
|
|
@@ -88,13 +76,20 @@ Check out the [Getting Started](https://render-engine.readthedocs.io/en/latest/p
|
|
|
88
76
|
|
|
89
77
|
## Finding Awesome Add-Ons
|
|
90
78
|
|
|
91
|
-
We've compiled a set of [awesome add-ons]
|
|
79
|
+
We've compiled a set of [awesome add-ons][awesome-list] that you can use to
|
|
80
|
+
make your site even better!
|
|
81
|
+
|
|
82
|
+
## CONTRIBUTING
|
|
83
|
+
|
|
84
|
+
We encourage contributors of all skill levels.
|
|
85
|
+
Please review the [contributing][contributing] section of our docs for more information.
|
|
92
86
|
|
|
93
87
|
## Contributors
|
|
94
88
|
|
|
89
|
+
<!-- markdownlint-disable -->
|
|
95
90
|
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
96
91
|
<!-- prettier-ignore-start -->
|
|
97
|
-
|
|
92
|
+
|
|
98
93
|
<table>
|
|
99
94
|
<tbody>
|
|
100
95
|
<tr>
|
|
@@ -126,20 +121,18 @@ We've compiled a set of [awesome add-ons](https://github.com/render-engine/rende
|
|
|
126
121
|
</tbody>
|
|
127
122
|
</table>
|
|
128
123
|
|
|
129
|
-
<!-- markdownlint-restore -->
|
|
130
124
|
<!-- prettier-ignore-end -->
|
|
131
|
-
|
|
132
125
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
133
|
-
<!-- prettier-ignore-start -->
|
|
134
|
-
<!-- markdownlint-disable -->
|
|
135
|
-
|
|
136
126
|
<!-- markdownlint-restore -->
|
|
137
|
-
<!-- prettier-ignore-end -->
|
|
138
|
-
|
|
139
|
-
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
140
127
|
|
|
141
128
|
### Sponsors at the $20/month and Higher Level
|
|
142
129
|
|
|
143
130
|
- [Brian Douglas](https://github.com/bdougie)
|
|
144
131
|
|
|
145
132
|
Thank you to them and all of those who continue to support this project!
|
|
133
|
+
|
|
134
|
+
[contributing]: https://render-engine.readthedocs.io/en/latest/contributing/CONTRIBUTING/
|
|
135
|
+
[page-docs]: https://render-engine.readthedocs.io/en/latest/page/
|
|
136
|
+
[collection-docs]: https://render-engine.readthedocs.io/en/latest/collection/
|
|
137
|
+
[site-docs]: https://render-engine.readthedocs.io/en/latest/site/
|
|
138
|
+
[awesome-list]: https://github.com/render-engine/render-engine-awesome-list
|
|
@@ -1,30 +1,28 @@
|
|
|
1
1
|
render_engine/.gitignore,sha256=74oa8YR8gNxKxB6lCtoqmgtB2xpZnWM539Qsl4wI_lg,12
|
|
2
2
|
render_engine/__init__.py,sha256=3fgua4ZA9o1pvQ5unhY1gRARLXFqAu019NEYqZTjP20,154
|
|
3
|
-
render_engine/
|
|
4
|
-
render_engine/__version__.py,sha256=pI2d9qsCwWR0-5U4R9QwGAWwJN7Kas0dzZtgcWUBLBU,718
|
|
3
|
+
render_engine/__version__.py,sha256=Adg32837GXzmF14xSZl3K1spnzU9xP6UbqfY6CVoOyg,718
|
|
5
4
|
render_engine/_base_object.py,sha256=B05rDWJUe5GM5FF94NeYY71KweBHY-IYel1lSBwLOrU,3574
|
|
6
5
|
render_engine/archive.py,sha256=S3-kCmDNVKkEfKDKxcEk-sXkBD0vS0RDnFfPunYkU8g,2072
|
|
7
6
|
render_engine/blog.py,sha256=f9GqFUFsta0KZnFhCiajobpfQyALqvgI5sbLm6zt1zw,1571
|
|
8
|
-
render_engine/collection.py,sha256=
|
|
9
|
-
render_engine/engine.py,sha256=
|
|
10
|
-
render_engine/feeds.py,sha256=
|
|
7
|
+
render_engine/collection.py,sha256=3CdT7Q56beYwjEYwA0scaQoLFfF54o8A3l-p21bBS6s,12633
|
|
8
|
+
render_engine/engine.py,sha256=CZ3ruTOsnwDw9O90O4Wy9HSWl1sysGFc8KIJTX_9v34,3173
|
|
9
|
+
render_engine/feeds.py,sha256=hI6jXeMchdZh85vUmOaRkI228CChRNU_cRKg1vaxTAQ,1142
|
|
11
10
|
render_engine/hookspecs.py,sha256=GhOpw0zTQjfwWOFYYbJ4P7Cvq-oy1MmTPHmd90dr0kg,2292
|
|
12
11
|
render_engine/links.py,sha256=pKmQMTz8-yGX8IecHcrlF3Dkejk7cptaO3qCkQiHB9I,2560
|
|
13
|
-
render_engine/page.py,sha256=
|
|
14
|
-
render_engine/plugins.py,sha256=
|
|
12
|
+
render_engine/page.py,sha256=gAcb8XxknAUfMMgG71wsDpI24z_dz0d2C3Ks7ST7RXU,9293
|
|
13
|
+
render_engine/plugins.py,sha256=QT_jvbKVlUz7GcL13f38Agli7t9flFDvG-qVj3yBRRU,4696
|
|
15
14
|
render_engine/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
render_engine/site.py,sha256=
|
|
17
|
-
render_engine/site_map.py,sha256=
|
|
18
|
-
render_engine/themes.py,sha256=
|
|
15
|
+
render_engine/site.py,sha256=pbwEdgR2TN0pzAuABFB8Z5bKRnmjETKXZZb3S6CJXz4,12774
|
|
16
|
+
render_engine/site_map.py,sha256=aiW9CdlPrGanSirQi_C2tbDr6281OAxlx9HHAhSucb4,5461
|
|
17
|
+
render_engine/themes.py,sha256=4ZYueERJAehz0CrhMV9LNuOBCWeNvP9bJSaOyGQGs2I,5069
|
|
19
18
|
render_engine/content_managers/__init__.py,sha256=z1x99J0GNcfqYFrugD0EleiZR6b-sfM6zViDTH1iF0s,161
|
|
20
|
-
render_engine/content_managers/base_content_manager.py,sha256=
|
|
21
|
-
render_engine/content_managers/file_content_manager.py,sha256=
|
|
22
|
-
render_engine/extras/__init__.py,sha256=L4jr4A7Jl-ODnSx1q2fP3_dBo37Dw6yepNRddu1nFNo,72
|
|
19
|
+
render_engine/content_managers/base_content_manager.py,sha256=Bgmk9LCRe0hdomkw_gm-4SR5v1hdE5srQGiDR25mZxY,1314
|
|
20
|
+
render_engine/content_managers/file_content_manager.py,sha256=QmpCz1qncmXmSY_SdpMCdvtCB5fbpKkONont7wVAPaI,2945
|
|
23
21
|
render_engine/parsers/markdown.py,sha256=0jpixCaoHaL0IRSvFIljJIRCvFkXoKTEYQNK38LwMDU,287
|
|
24
22
|
render_engine/render_engine_templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
23
|
render_engine/render_engine_templates/archive.html,sha256=Ipxtj2vUSMk09pcjiGpNRIH3dWTc6naomVQ0Otp1O00,45
|
|
26
24
|
render_engine/render_engine_templates/base.html,sha256=IJv85-lU8Fq4HBe1p1PWl3-M3SvqxVYvQvfRwp9viTI,42
|
|
27
|
-
render_engine/render_engine_templates/base_collection_path.md,sha256=
|
|
25
|
+
render_engine/render_engine_templates/base_collection_path.md,sha256=nWY3B8tqbuJ0wdROntmtYmyX7KBbgiq7QvDY-msvS7s,431
|
|
28
26
|
render_engine/render_engine_templates/page.html,sha256=p9aZ6_6mouPT1c8FVg-f14MeSt4OB-sH3hcXTY6jGmA,42
|
|
29
27
|
render_engine/render_engine_templates/rss2.0.xml,sha256=Z5LGUdPRr3fQhXtJCjR8uxiPQ_GqYHELleDaS8Tr1oU,1803
|
|
30
28
|
render_engine/render_engine_templates/rss2.0_items.xml,sha256=jxhDG9K2R112-upqq4NZnahDGZcvlWIn_sl9b4ArVEI,1306
|
|
@@ -36,7 +34,7 @@ render_engine/render_engine_templates/base_templates/_page.html,sha256=jjrY2BAwl
|
|
|
36
34
|
render_engine/render_engine_templates/components/footer.html,sha256=HkPGGhfN0HcYm7t8zgXWCQ3bsCbT8FxT4_n2-9e1zUE,74
|
|
37
35
|
render_engine/render_engine_templates/components/page_title.html,sha256=l8aE1TY94UPHXHqAyy6jv4IoN2Hv9cbrTPh7ILkMyxg,137
|
|
38
36
|
render_engine/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
|
-
render_engine-2026.
|
|
40
|
-
render_engine-2026.
|
|
41
|
-
render_engine-2026.
|
|
42
|
-
render_engine-2026.
|
|
37
|
+
render_engine-2026.2.1a1.dist-info/METADATA,sha256=zF9jFepRfk-g7IPXiDHZMgU2VbOBuL-t4w1IiNj5o8g,11482
|
|
38
|
+
render_engine-2026.2.1a1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
39
|
+
render_engine-2026.2.1a1.dist-info/top_level.txt,sha256=aNGALDMsFyrusho04AvUjSivsgEE9tQp_LP_jGr312Q,14
|
|
40
|
+
render_engine-2026.2.1a1.dist-info/RECORD,,
|
render_engine/__main__.py
DELETED
render_engine/extras/__init__.py
DELETED
|
File without changes
|