render-engine 2025.9.1a2__tar.gz → 2025.10.1__tar.gz
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-2025.9.1a2 → render_engine-2025.10.1}/PKG-INFO +1 -1
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/page.md +14 -13
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/site_map.md +8 -2
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/page.py +32 -11
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/render_engine_templates/sitemap.xml +2 -1
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/render_engine_templates/sitemap_item.xml +2 -2
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/site.py +12 -3
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/site_map.py +13 -1
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine.egg-info/PKG-INFO +1 -1
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/tests/test_page.py +69 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/tests/test_site_map.py +70 -20
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.all-contributorsrc +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.devcontainer/Dockerfile +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.devcontainer/devcontainer.json +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.devcontainer/setup.sh +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.github/FUNDING.yml +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.github/ISSUE_TEMPLATE/config.yaml +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.github/ISSUE_TEMPLATE/form_issue_template.yml +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.github/dependabot-bot.yml +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.github/dependabot.yml +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.github/labeler.yml +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.github/release.yml +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.github/workflows/devcontainer-ci.yml +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.github/workflows/labeler.yml +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.github/workflows/lint.yml +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.github/workflows/publish.yml +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.github/workflows/scorecard.yml +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.github/workflows/test.yml +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.gitignore +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.markdownlint.json +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.pre-commit-config.yaml +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.readthedocs.yml +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/.vscode/tasks.json +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/CONTRIBUTING.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/README.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/SECURITY.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/.markdownlint.json +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/archive.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/assets/create environment vs code.gif +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/assets/create-app-help.png +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/assets/create-codespace.gif +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/assets/launching a dev container.gif +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/assets/render-engine-init-help.png +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/assets/render-engine-init.png +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/cli.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/collection.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/contributing/CODE_OF_CONDUCT.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/contributing/CONTRIBUTING.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/contributing/environment_setup.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/custom_collections.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/feeds.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/getting-started/building-your-site.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/getting-started/creating-a-collection.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/getting-started/creating-a-page.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/getting-started/creating-your-app.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/getting-started/getting-started.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/getting-started/installation.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/getting-started/layout.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/index.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/parsers.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/plugins.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/site.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/templates.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/theme_management.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/mkdocs.yml +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/requirements.txt +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/noxfile.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/pyproject.toml +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/requirements.txt +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/setup.cfg +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/.gitignore +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/__init__.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/__main__.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/_base_object.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/archive.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/blog.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/collection.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/engine.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/extras/__init__.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/feeds.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/hookspecs.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/links.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/parsers/markdown.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/plugins.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/py.typed +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/render_engine_templates/__init__.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/render_engine_templates/archive.html +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/render_engine_templates/base.html +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/render_engine_templates/base_collection_path.md +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/render_engine_templates/base_templates/_archive.html +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/render_engine_templates/base_templates/_base.html +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/render_engine_templates/base_templates/_page.html +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/render_engine_templates/components/footer.html +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/render_engine_templates/components/page_title.html +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/render_engine_templates/page.html +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/render_engine_templates/rss2.0.xml +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/render_engine_templates/rss2.0_items.xml +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/themes.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine/utils/__init__.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine.egg-info/SOURCES.txt +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine.egg-info/dependency_links.txt +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine.egg-info/requires.txt +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine.egg-info/top_level.txt +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/tests/conftest.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/tests/test_archive.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/tests/test_base_object.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/tests/test_blog.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/tests/test_collections.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/tests/test_engine.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/tests/test_feeds/conftest_feed.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/tests/test_feeds/test_feeds.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/tests/test_parsers_remove_2024_3_1.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/tests/test_plugins.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/tests/test_site.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/tests/test_templates/test_base_html.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/tests/test_theme_manager.py +0 -0
- {render_engine-2025.9.1a2 → render_engine-2025.10.1}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: render_engine
|
|
3
|
-
Version: 2025.
|
|
3
|
+
Version: 2025.10.1
|
|
4
4
|
Summary: A Flexible Static Site Generator for Python
|
|
5
5
|
Project-URL: homepage, https://github.com/render-engine/render-engine/
|
|
6
6
|
Project-URL: repository, https://github.com/render-engine/render-engine/
|
|
@@ -63,17 +63,18 @@ When you create a page, you specify variables passed into rendering template.
|
|
|
63
63
|
|
|
64
64
|
**Attributes:**
|
|
65
65
|
|
|
66
|
-
| Name | Type
|
|
67
|
-
| ---
|
|
68
|
-
| `content_path` | `str
|
|
69
|
-
| `extension` | `str
|
|
70
|
-
| `engine` | `str
|
|
71
|
-
| `reference` | `str
|
|
72
|
-
| `routes` | `str
|
|
73
|
-
| `template` | `str
|
|
74
|
-
| `Parser` | `type[BasePageParser]`
|
|
75
|
-
| `title` | `str`
|
|
76
|
-
| `skip_site_map` | `
|
|
66
|
+
| Name | Type | Description |
|
|
67
|
+
| --- |--------------------------|----------------------------------------------------------------------------------------------------------------------------------------------|
|
|
68
|
+
| `content_path` | `str \| None` | The path to the file that will be used to generate the Page's `content`. |
|
|
69
|
+
| `extension` | `str \| None` | The suffix to use for the page. Defaults to `.html`. |
|
|
70
|
+
| `engine` | `str \| None` | If present, the engine to use for rendering the page. **This is normally not set and the `Site` 's engine will be used.** |
|
|
71
|
+
| `reference` | `str \| None` | Used to determine how to reference the page in the `Site`'s route_list. Defaults to `slug`. |
|
|
72
|
+
| `routes` | `str \| None` | The routes to use for the page. Defaults to `["./"]`. |
|
|
73
|
+
| `template` | `str \| None` | The template used to render the page. If not provided, the `Site`'s `content`will be used. |
|
|
74
|
+
| `Parser` | `type[BasePageParser]` | The parser to generate the page's `raw_content`. Defaults to `BasePageParser`. |
|
|
75
|
+
| `title` | `str` | The title of the page. Defaults to the class name. |
|
|
76
|
+
| `skip_site_map` | `bool` | When set to `True` the `Page` will not be included in the generated `SiteMap`. Defaults to `False`. |
|
|
77
|
+
| `no_prerender` | `bool` | When set to `True` the `Page`'s `content` will not be pre-rendered as a `Template` even if `{{ site_map }}` is present. Defaults to `False`. |
|
|
77
78
|
|
|
78
79
|
## About Page Attributes
|
|
79
80
|
|
|
@@ -109,8 +110,8 @@ By default Page._content will return the result of `Page.Parser.parse(Page.conte
|
|
|
109
110
|
### Accessing URLs for other pages in the site from within the page content
|
|
110
111
|
|
|
111
112
|
In order to allow lookup of URLs for other pages within a Site the `content` of a page may be
|
|
112
|
-
a template. If the `content` matches the pattern `{{.*?}}` we will render the `content` as a
|
|
113
|
-
template prior to rendering the page itself
|
|
113
|
+
a template. If the `content` matches the pattern `{{.*?site_map.*?}}` we will render the `content` as a
|
|
114
|
+
template prior to rendering the page itself unless the `no_prerender` attribute of the page is `True`.
|
|
114
115
|
|
|
115
116
|
Example:
|
|
116
117
|
|
|
@@ -41,7 +41,7 @@ one found.
|
|
|
41
41
|
The `SiteMap` object has an `html` property that will return an HTML sitemap with _absolute_ URLs with the
|
|
42
42
|
`Site`'s `SITE_URL`.
|
|
43
43
|
|
|
44
|
-
As a convenience, Render Engine will generate a site map page if the `Site` property of `
|
|
44
|
+
As a convenience, Render Engine will generate a site map page if the `Site` property of `render_html_site_map`
|
|
45
45
|
is `True` (it defaults to `False`.) Please note that this will not be templated. Should you wish the generated
|
|
46
46
|
site map to be on a template you can add the following to your app:
|
|
47
47
|
|
|
@@ -53,7 +53,13 @@ class SiteMapPage(Page):
|
|
|
53
53
|
skip_site_map = True
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
Please note the `skip_site_map = True` to avoid having a self
|
|
56
|
+
Please note the `skip_site_map = True` to avoid having a self-referential link to the site map.
|
|
57
|
+
|
|
58
|
+
## Generating an XML site map
|
|
59
|
+
|
|
60
|
+
To have your site generate an XML site map set the `render_xml_site_map` property of your `Site` object
|
|
61
|
+
to `True` (defaults to `False`.) This will create the `site_map.xml` file in the root output directory
|
|
62
|
+
of your site.
|
|
57
63
|
|
|
58
64
|
## The `SiteMapEntry` object
|
|
59
65
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import re
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
from typing import Any
|
|
@@ -10,6 +11,8 @@ from render_engine.themes import ThemeManager
|
|
|
10
11
|
from ._base_object import BaseObject
|
|
11
12
|
from .plugins import PluginManager
|
|
12
13
|
|
|
14
|
+
logger = logging.getLogger("Page")
|
|
15
|
+
|
|
13
16
|
|
|
14
17
|
class BasePage(BaseObject):
|
|
15
18
|
"""
|
|
@@ -28,6 +31,8 @@ class BasePage(BaseObject):
|
|
|
28
31
|
extension (str): The file extension for the page. Defaults to ".html".
|
|
29
32
|
routes (list[str] | Path): The list of routes for the page. Defaults to ["./"].
|
|
30
33
|
template (str | Template | None): The template to use for rendering the page.
|
|
34
|
+
site: The Site object that owns the page.
|
|
35
|
+
no_prerender: Flag to not prerender the content
|
|
31
36
|
"""
|
|
32
37
|
|
|
33
38
|
extension: str = ".html"
|
|
@@ -36,7 +41,8 @@ class BasePage(BaseObject):
|
|
|
36
41
|
rendered_content: str | None
|
|
37
42
|
_reference: str = "_slug"
|
|
38
43
|
plugin_manager: PluginManager | None
|
|
39
|
-
site = None
|
|
44
|
+
site = None # This is a Site but circular imports so we can't actually type hint it.
|
|
45
|
+
no_prerender: bool = False
|
|
40
46
|
|
|
41
47
|
@property
|
|
42
48
|
def _content(self) -> any:
|
|
@@ -69,20 +75,35 @@ class BasePage(BaseObject):
|
|
|
69
75
|
return f"/{route}/{self.path_name}"
|
|
70
76
|
|
|
71
77
|
def _render_from_template(self, template: Template, **kwargs) -> str:
|
|
72
|
-
"""
|
|
78
|
+
"""
|
|
79
|
+
Renders the page from a template.
|
|
80
|
+
|
|
81
|
+
If the content looks like a template that
|
|
82
|
+
|
|
83
|
+
:param template: Template to render
|
|
84
|
+
:param **kwargs: Data to pass into the template for rendering.
|
|
85
|
+
:return: The rendered page
|
|
86
|
+
"""
|
|
73
87
|
template_data = {"data": self._data, "content": self._content}
|
|
74
88
|
if site := getattr(self, "site", None):
|
|
75
89
|
template_data["site_map"] = site.site_map
|
|
76
|
-
if isinstance(self._content, str) and re.search(r"{{
|
|
90
|
+
if not self.no_prerender and isinstance(self._content, str) and re.search(r"{{.*?site_map.*?}}", self._content):
|
|
77
91
|
# If the content looks like a template, try to render it.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
92
|
+
try:
|
|
93
|
+
content_template = Template(self._content)
|
|
94
|
+
except Exception:
|
|
95
|
+
logger.info(f"Failed to parse {repr(self.path_name)} as a template.", exc_info=True)
|
|
96
|
+
else:
|
|
97
|
+
try:
|
|
98
|
+
template_data["content"] = content_template.render(
|
|
99
|
+
**{
|
|
100
|
+
**self.to_dict(),
|
|
101
|
+
**template_data,
|
|
102
|
+
**kwargs,
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
except Exception:
|
|
106
|
+
logger.info(f"Failed to pre-render {repr(self.path_name)}.", exc_info=True)
|
|
86
107
|
|
|
87
108
|
return template.render(
|
|
88
109
|
**{
|
|
@@ -1,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>
|
|
@@ -21,7 +21,8 @@ class Site:
|
|
|
21
21
|
Attributes:
|
|
22
22
|
site_vars (dict): A dictionary containing site-wide variables and their values.
|
|
23
23
|
plugin_settings (dict): A dictionary containing plugin settings.
|
|
24
|
-
|
|
24
|
+
render_html_site_map (bool): Whether to render the generated site map as an HTML page.
|
|
25
|
+
render_xml_site_map (bool): Whether to render the generated site map as XML.
|
|
25
26
|
|
|
26
27
|
Methods:
|
|
27
28
|
update_site_vars(**kwargs): Updates the site-wide variables with the given key-value pairs.
|
|
@@ -51,7 +52,8 @@ class Site:
|
|
|
51
52
|
_template_path: str | Path = "templates"
|
|
52
53
|
_static_paths: set = {"static"}
|
|
53
54
|
plugin_settings: dict = {"plugins": defaultdict(dict)}
|
|
54
|
-
|
|
55
|
+
render_html_site_map: bool = False
|
|
56
|
+
render_xml_site_map: bool = False
|
|
55
57
|
|
|
56
58
|
def __init__(
|
|
57
59
|
self,
|
|
@@ -253,7 +255,7 @@ class Site:
|
|
|
253
255
|
with Progress() as progress:
|
|
254
256
|
task_site_map = progress.add_task("Generating site map", total=1)
|
|
255
257
|
self._site_map = SiteMap(self.route_list, self.site_vars.get("SITE_URL", ""))
|
|
256
|
-
if self.
|
|
258
|
+
if self.render_html_site_map:
|
|
257
259
|
|
|
258
260
|
@self.page
|
|
259
261
|
class SiteMapPage(Page):
|
|
@@ -262,6 +264,13 @@ class Site:
|
|
|
262
264
|
content = self._site_map.html
|
|
263
265
|
template = "page.html"
|
|
264
266
|
|
|
267
|
+
if self.render_xml_site_map:
|
|
268
|
+
|
|
269
|
+
@self.page
|
|
270
|
+
class SiteMapXml(Page):
|
|
271
|
+
path_name = "site_map.xml"
|
|
272
|
+
template = "sitemap.xml"
|
|
273
|
+
|
|
265
274
|
progress.update(task_site_map, advance=1)
|
|
266
275
|
|
|
267
276
|
pre_build_task = progress.add_task("Loading Pre-Build Plugins and Themes", total=1)
|
|
@@ -22,7 +22,7 @@ class SiteMapEntry:
|
|
|
22
22
|
self._route = f"/{route.lstrip('/')}/{self.path_name}" if from_collection else f"/{self.path_name}"
|
|
23
23
|
self.entries = list()
|
|
24
24
|
case Collection():
|
|
25
|
-
self._route = f"/{
|
|
25
|
+
self._route = f"/{entry.routes[0].lstrip('/')}"
|
|
26
26
|
self.entries = [
|
|
27
27
|
SiteMapEntry(collection_entry, self._route, from_collection=True) for collection_entry in entry
|
|
28
28
|
]
|
|
@@ -34,6 +34,10 @@ class SiteMapEntry:
|
|
|
34
34
|
"""The URL for the given entry"""
|
|
35
35
|
return str(self._route)
|
|
36
36
|
|
|
37
|
+
def __str__(self) -> str:
|
|
38
|
+
"""String representation of the entry as its URL"""
|
|
39
|
+
return self.url_for
|
|
40
|
+
|
|
37
41
|
|
|
38
42
|
class SiteMap:
|
|
39
43
|
"""Site map"""
|
|
@@ -58,6 +62,12 @@ class SiteMap:
|
|
|
58
62
|
self._collections[sm_entry.slug] = sm_entry
|
|
59
63
|
self.site_url = site_url
|
|
60
64
|
|
|
65
|
+
def __iter__(self):
|
|
66
|
+
"""Iterator for the site map object"""
|
|
67
|
+
for entry in self._route_map.values():
|
|
68
|
+
yield entry
|
|
69
|
+
yield from entry.entries
|
|
70
|
+
|
|
61
71
|
def find(
|
|
62
72
|
self,
|
|
63
73
|
value: str,
|
|
@@ -121,6 +131,8 @@ class SiteMap:
|
|
|
121
131
|
def html(self) -> str:
|
|
122
132
|
"""Build the site map as HTML"""
|
|
123
133
|
html_string = "<ul>\n"
|
|
134
|
+
# We can't iterate over `self` because that will flatten out the site map and we do not want that for the HTML
|
|
135
|
+
# version.
|
|
124
136
|
for entry in self._route_map.values():
|
|
125
137
|
html_string += f'\t<li><a href="{urljoin(self.site_url, entry.url_for)}">{entry.title}</a></li>\n'
|
|
126
138
|
if entry.entries:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: render_engine
|
|
3
|
-
Version: 2025.
|
|
3
|
+
Version: 2025.10.1
|
|
4
4
|
Summary: A Flexible Static Site Generator for Python
|
|
5
5
|
Project-URL: homepage, https://github.com/render-engine/render-engine/
|
|
6
6
|
Project-URL: repository, https://github.com/render-engine/render-engine/
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import pathlib
|
|
2
3
|
|
|
3
4
|
import jinja2
|
|
@@ -96,3 +97,71 @@ def test_rendered_page_from_template_has_data():
|
|
|
96
97
|
data = [1, 2, 3, 4]
|
|
97
98
|
|
|
98
99
|
assert CustomPage()._render_from_template(template=CustomPage.template) == "1234"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_page_fails_to_render_content_as_template(caplog):
|
|
103
|
+
"""Tests handling content that fails to render as a template"""
|
|
104
|
+
caplog.set_level(logging.INFO)
|
|
105
|
+
|
|
106
|
+
template = "{% for d in data %}{{d}}{% endfor %}\n{{content}}"
|
|
107
|
+
|
|
108
|
+
environment = jinja2.Environment(loader=jinja2.DictLoader({"test.html": template}))
|
|
109
|
+
|
|
110
|
+
class CustomPage(Page):
|
|
111
|
+
template = environment.get_template("test.html")
|
|
112
|
+
data = [1, 2, 3, 4]
|
|
113
|
+
content = "{{ site_map.find('test') }}"
|
|
114
|
+
|
|
115
|
+
assert CustomPage()._render_from_template(template=CustomPage.template) == "1234\n{{ site_map.find('test') }}"
|
|
116
|
+
assert "Failed to pre-render" in caplog.messages[0]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_braces_ignored_without_sitemap():
|
|
120
|
+
"""Tests that braces in content are ignored without `site_map`"""
|
|
121
|
+
template = "{% for d in data %}{{d}}{% endfor %}\n{{content}}"
|
|
122
|
+
|
|
123
|
+
environment = jinja2.Environment(loader=jinja2.DictLoader({"test.html": template}))
|
|
124
|
+
|
|
125
|
+
class CustomPage(Page):
|
|
126
|
+
template = environment.get_template("test.html")
|
|
127
|
+
data = [1, 2, 3, 4]
|
|
128
|
+
content = "{{ example }}"
|
|
129
|
+
|
|
130
|
+
assert CustomPage()._render_from_template(template=CustomPage.template) == "1234\n{{ example }}"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_exception_in_parsing_content_as_template(monkeypatch, caplog):
|
|
134
|
+
"""Test failing to parse content as template"""
|
|
135
|
+
|
|
136
|
+
def mock_template():
|
|
137
|
+
raise jinja2.exceptions.UndefinedError("Mocked error")
|
|
138
|
+
|
|
139
|
+
monkeypatch.setattr("render_engine.page.Template", mock_template)
|
|
140
|
+
caplog.set_level(logging.INFO)
|
|
141
|
+
|
|
142
|
+
template = "{% for d in data %}{{d}}{% endfor %}\n{{content}}"
|
|
143
|
+
|
|
144
|
+
environment = jinja2.Environment(loader=jinja2.DictLoader({"test.html": template}))
|
|
145
|
+
|
|
146
|
+
class CustomPage(Page):
|
|
147
|
+
template = environment.get_template("test.html")
|
|
148
|
+
data = [1, 2, 3, 4]
|
|
149
|
+
content = "{{ site_map.find('test') }}"
|
|
150
|
+
|
|
151
|
+
assert CustomPage()._render_from_template(template=CustomPage.template) == "1234\n{{ site_map.find('test') }}"
|
|
152
|
+
assert "Failed to parse" in caplog.messages[0]
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_no_prerender():
|
|
156
|
+
"""Test pre-rendering does not occur when no_prerender is True"""
|
|
157
|
+
template = "{% for d in data %}{{d}}{% endfor %}\n{{content}}"
|
|
158
|
+
|
|
159
|
+
environment = jinja2.Environment(loader=jinja2.DictLoader({"test.html": template}))
|
|
160
|
+
|
|
161
|
+
class CustomPage(Page):
|
|
162
|
+
template = environment.get_template("test.html")
|
|
163
|
+
data = [1, 2, 3, 4]
|
|
164
|
+
content = "{{ site_map.find('test') }}"
|
|
165
|
+
no_prerender = True
|
|
166
|
+
|
|
167
|
+
assert CustomPage()._render_from_template(template=CustomPage.template) == "1234\n{{ site_map.find('test') }}"
|
|
@@ -60,7 +60,7 @@ def site(tmp_path_factory):
|
|
|
60
60
|
for collection in ["coll1", "coll2"]:
|
|
61
61
|
|
|
62
62
|
class MyColl(Collection):
|
|
63
|
-
routes = [collection]
|
|
63
|
+
routes = [f"{collection}-route"]
|
|
64
64
|
pages = coll_pages[collection]
|
|
65
65
|
|
|
66
66
|
collection_class = MyColl
|
|
@@ -82,19 +82,19 @@ def test_site_map_to_html(site):
|
|
|
82
82
|
sm = SiteMap(site.route_list, "")
|
|
83
83
|
assert sm.html == (
|
|
84
84
|
"<ul>\n"
|
|
85
|
-
'\t<li><a href="/coll1">coll1</a></li>\n'
|
|
85
|
+
'\t<li><a href="/coll1-route">coll1</a></li>\n'
|
|
86
86
|
"\t<ul>\n"
|
|
87
|
-
'\t\t<li><a href="/coll1/page0.html">coll1 -- Page 0</a></li>\n'
|
|
88
|
-
'\t\t<li><a href="/coll1/page1.html">coll1 -- Page 1</a></li>\n'
|
|
89
|
-
'\t\t<li><a href="/coll1/page2.html">coll1 -- Page 2</a></li>\n'
|
|
90
|
-
'\t\t<li><a href="/coll1/page3.html">coll1 -- Page 3</a></li>\n'
|
|
87
|
+
'\t\t<li><a href="/coll1-route/page0.html">coll1 -- Page 0</a></li>\n'
|
|
88
|
+
'\t\t<li><a href="/coll1-route/page1.html">coll1 -- Page 1</a></li>\n'
|
|
89
|
+
'\t\t<li><a href="/coll1-route/page2.html">coll1 -- Page 2</a></li>\n'
|
|
90
|
+
'\t\t<li><a href="/coll1-route/page3.html">coll1 -- Page 3</a></li>\n'
|
|
91
91
|
"\t</ul>\n"
|
|
92
|
-
'\t<li><a href="/coll2">coll2</a></li>\n'
|
|
92
|
+
'\t<li><a href="/coll2-route">coll2</a></li>\n'
|
|
93
93
|
"\t<ul>\n"
|
|
94
|
-
'\t\t<li><a href="/coll2/page0.html">coll2 -- Page 0</a></li>\n'
|
|
95
|
-
'\t\t<li><a href="/coll2/page1.html">coll2 -- Page 1</a></li>\n'
|
|
96
|
-
'\t\t<li><a href="/coll2/page2.html">coll2 -- Page 2</a></li>\n'
|
|
97
|
-
'\t\t<li><a href="/coll2/page3.html">coll2 -- Page 3</a></li>\n'
|
|
94
|
+
'\t\t<li><a href="/coll2-route/page0.html">coll2 -- Page 0</a></li>\n'
|
|
95
|
+
'\t\t<li><a href="/coll2-route/page1.html">coll2 -- Page 1</a></li>\n'
|
|
96
|
+
'\t\t<li><a href="/coll2-route/page2.html">coll2 -- Page 2</a></li>\n'
|
|
97
|
+
'\t\t<li><a href="/coll2-route/page3.html">coll2 -- Page 3</a></li>\n'
|
|
98
98
|
"\t</ul>\n"
|
|
99
99
|
'\t<li><a href="/page0.html">Page 0</a></li>\n'
|
|
100
100
|
'\t<li><a href="/page1.html">Page 1</a></li>\n'
|
|
@@ -109,21 +109,21 @@ def test_site_map_to_html(site):
|
|
|
109
109
|
("page1", {}, "/page1.html"),
|
|
110
110
|
("page1", {"attr": "slug"}, "/page1.html"),
|
|
111
111
|
("page3", {"attr": "slug"}, None),
|
|
112
|
-
("page3", {"attr": "slug", "full_search": True}, "/coll1/page3.html"),
|
|
113
|
-
("page3", {"attr": "slug", "collection": "coll2"}, "/coll2/page3.html"),
|
|
112
|
+
("page3", {"attr": "slug", "full_search": True}, "/coll1-route/page3.html"),
|
|
113
|
+
("page3", {"attr": "slug", "collection": "coll2"}, "/coll2-route/page3.html"),
|
|
114
114
|
("page1.html", {"attr": "path_name"}, "/page1.html"),
|
|
115
115
|
("page3.html", {"attr": "path_name"}, None),
|
|
116
|
-
("page3.html", {"attr": "path_name", "full_search": True}, "/coll1/page3.html"),
|
|
117
|
-
("page3.html", {"attr": "path_name", "collection": "coll2"}, "/coll2/page3.html"),
|
|
116
|
+
("page3.html", {"attr": "path_name", "full_search": True}, "/coll1-route/page3.html"),
|
|
117
|
+
("page3.html", {"attr": "path_name", "collection": "coll2"}, "/coll2-route/page3.html"),
|
|
118
118
|
("page1", {"attr": "slug"}, "/page1.html"),
|
|
119
119
|
("page3", {"attr": "slug"}, None),
|
|
120
|
-
("page3", {"attr": "slug", "full_search": True}, "/coll1/page3.html"),
|
|
121
|
-
("page3", {"attr": "slug", "collection": "coll2"}, "/coll2/page3.html"),
|
|
120
|
+
("page3", {"attr": "slug", "full_search": True}, "/coll1-route/page3.html"),
|
|
121
|
+
("page3", {"attr": "slug", "collection": "coll2"}, "/coll2-route/page3.html"),
|
|
122
122
|
("Page 1", {"attr": "title"}, "/page1.html"),
|
|
123
123
|
("coll1 -- Page 3", {"attr": "title"}, None),
|
|
124
|
-
("coll1 -- Page 3", {"attr": "title", "full_search": True}, "/coll1/page3.html"),
|
|
124
|
+
("coll1 -- Page 3", {"attr": "title", "full_search": True}, "/coll1-route/page3.html"),
|
|
125
125
|
("coll1 -- Page 3", {"attr": "title", "collection": "coll2"}, None),
|
|
126
|
-
("coll2 -- Page 3", {"attr": "title", "collection": "coll2"}, "/coll2/page3.html"),
|
|
126
|
+
("coll2 -- Page 3", {"attr": "title", "collection": "coll2"}, "/coll2-route/page3.html"),
|
|
127
127
|
],
|
|
128
128
|
)
|
|
129
129
|
def test_site_map_search(site, value, params, expected):
|
|
@@ -137,4 +137,54 @@ def test_site_map_search(site, value, params, expected):
|
|
|
137
137
|
|
|
138
138
|
def test_find_in_template(site):
|
|
139
139
|
site.render()
|
|
140
|
-
assert (site.output_path / "page1.html").read_text() == "Page 1\n/coll1/page0.html"
|
|
140
|
+
assert (site.output_path / "page1.html").read_text() == "Page 1\n/coll1-route/page0.html"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_site_map_to_xml(site):
|
|
144
|
+
site.render_xml_site_map = True
|
|
145
|
+
site.render()
|
|
146
|
+
assert (
|
|
147
|
+
(site.output_path / "site_map.xml").read_text()
|
|
148
|
+
== """<?xml version="1.0" encoding="UTF-8"?>
|
|
149
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
150
|
+
<url>
|
|
151
|
+
<loc>http://localhost:8000/coll1-route</loc>
|
|
152
|
+
</url>
|
|
153
|
+
<url>
|
|
154
|
+
<loc>http://localhost:8000/coll1-route/page0.html</loc>
|
|
155
|
+
</url>
|
|
156
|
+
<url>
|
|
157
|
+
<loc>http://localhost:8000/coll1-route/page1.html</loc>
|
|
158
|
+
</url>
|
|
159
|
+
<url>
|
|
160
|
+
<loc>http://localhost:8000/coll1-route/page2.html</loc>
|
|
161
|
+
</url>
|
|
162
|
+
<url>
|
|
163
|
+
<loc>http://localhost:8000/coll1-route/page3.html</loc>
|
|
164
|
+
</url>
|
|
165
|
+
<url>
|
|
166
|
+
<loc>http://localhost:8000/coll2-route</loc>
|
|
167
|
+
</url>
|
|
168
|
+
<url>
|
|
169
|
+
<loc>http://localhost:8000/coll2-route/page0.html</loc>
|
|
170
|
+
</url>
|
|
171
|
+
<url>
|
|
172
|
+
<loc>http://localhost:8000/coll2-route/page1.html</loc>
|
|
173
|
+
</url>
|
|
174
|
+
<url>
|
|
175
|
+
<loc>http://localhost:8000/coll2-route/page2.html</loc>
|
|
176
|
+
</url>
|
|
177
|
+
<url>
|
|
178
|
+
<loc>http://localhost:8000/coll2-route/page3.html</loc>
|
|
179
|
+
</url>
|
|
180
|
+
<url>
|
|
181
|
+
<loc>http://localhost:8000/page0.html</loc>
|
|
182
|
+
</url>
|
|
183
|
+
<url>
|
|
184
|
+
<loc>http://localhost:8000/page1.html</loc>
|
|
185
|
+
</url>
|
|
186
|
+
<url>
|
|
187
|
+
<loc>http://localhost:8000/page2.html</loc>
|
|
188
|
+
</url>
|
|
189
|
+
</urlset>"""
|
|
190
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{render_engine-2025.9.1a2 → render_engine-2025.10.1}/.github/ISSUE_TEMPLATE/form_issue_template.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/assets/create environment vs code.gif
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/assets/launching a dev container.gif
RENAMED
|
File without changes
|
{render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/assets/render-engine-init-help.png
RENAMED
|
File without changes
|
{render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/assets/render-engine-init.png
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/contributing/CODE_OF_CONDUCT.md
RENAMED
|
File without changes
|
|
File without changes
|
{render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/contributing/environment_setup.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/getting-started/building-your-site.md
RENAMED
|
File without changes
|
|
File without changes
|
{render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/getting-started/creating-a-page.md
RENAMED
|
File without changes
|
{render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/getting-started/creating-your-app.md
RENAMED
|
File without changes
|
{render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/getting-started/getting-started.md
RENAMED
|
File without changes
|
{render_engine-2025.9.1a2 → render_engine-2025.10.1}/docs/docs/getting-started/installation.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine.egg-info/requires.txt
RENAMED
|
File without changes
|
{render_engine-2025.9.1a2 → render_engine-2025.10.1}/src/render_engine.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|