render-engine 2026.1.1a1__tar.gz → 2026.1.1a2__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.
Files changed (124) hide show
  1. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/PKG-INFO +1 -1
  2. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/content_manager.md +18 -0
  3. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/requirements.txt +1 -1
  4. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/__version__.py +3 -3
  5. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/collection.py +5 -4
  6. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/content_managers/base_content_manager.py +18 -1
  7. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/content_managers/file_content_manager.py +30 -1
  8. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/page.py +18 -13
  9. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/site.py +5 -8
  10. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine.egg-info/PKG-INFO +1 -1
  11. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine.egg-info/SOURCES.txt +1 -0
  12. render_engine-2026.1.1a2/tests/test_file_content_manager.py +78 -0
  13. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/tests/test_plugins.py +6 -6
  14. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/tests/test_templates/test_base_html.py +2 -2
  15. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.all-contributorsrc +0 -0
  16. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.devcontainer/Dockerfile +0 -0
  17. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.devcontainer/devcontainer.json +0 -0
  18. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.devcontainer/setup.sh +0 -0
  19. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.github/FUNDING.yml +0 -0
  20. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.github/ISSUE_TEMPLATE/config.yaml +0 -0
  21. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.github/ISSUE_TEMPLATE/form_issue_template.yml +0 -0
  22. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  23. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.github/dependabot-bot.yml +0 -0
  24. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.github/dependabot.yml +0 -0
  25. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.github/labeler.yml +0 -0
  26. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.github/release.yml +0 -0
  27. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.github/workflows/devcontainer-ci.yml +0 -0
  28. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.github/workflows/labeler.yml +0 -0
  29. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.github/workflows/lint.yml +0 -0
  30. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.github/workflows/publish.yml +0 -0
  31. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.github/workflows/scorecard.yml +0 -0
  32. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.github/workflows/test.yml +0 -0
  33. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.gitignore +0 -0
  34. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.markdownlint.json +0 -0
  35. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.pre-commit-config.yaml +0 -0
  36. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.readthedocs.yml +0 -0
  37. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/.vscode/tasks.json +0 -0
  38. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/CONTRIBUTING.md +0 -0
  39. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/README.md +0 -0
  40. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/SECURITY.md +0 -0
  41. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/.markdownlint.json +0 -0
  42. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/archive.md +0 -0
  43. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/assets/create environment vs code.gif +0 -0
  44. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/assets/create-app-help.png +0 -0
  45. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/assets/create-codespace.gif +0 -0
  46. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/assets/launching a dev container.gif +0 -0
  47. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/assets/render-engine-init-help.png +0 -0
  48. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/assets/render-engine-init.png +0 -0
  49. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/cli.md +0 -0
  50. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/collection.md +0 -0
  51. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/contributing/CODE_OF_CONDUCT.md +0 -0
  52. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/contributing/CONTRIBUTING.md +0 -0
  53. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/contributing/environment_setup.md +0 -0
  54. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/custom_collections.md +0 -0
  55. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/feeds.md +0 -0
  56. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/getting-started/building-your-site.md +0 -0
  57. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/getting-started/creating-a-collection.md +0 -0
  58. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/getting-started/creating-a-page.md +0 -0
  59. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/getting-started/creating-your-app.md +0 -0
  60. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/getting-started/getting-started.md +0 -0
  61. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/getting-started/installation.md +0 -0
  62. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/getting-started/layout.md +0 -0
  63. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/index.md +0 -0
  64. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/page.md +0 -0
  65. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/parsers.md +0 -0
  66. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/plugins.md +0 -0
  67. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/site.md +0 -0
  68. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/site_map.md +0 -0
  69. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/templates.md +0 -0
  70. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/docs/theme_management.md +0 -0
  71. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/mkdocs.yml +0 -0
  72. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/docs/requirements.txt +0 -0
  73. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/noxfile.py +0 -0
  74. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/pyproject.toml +0 -0
  75. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/setup.cfg +0 -0
  76. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/.gitignore +0 -0
  77. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/__init__.py +0 -0
  78. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/__main__.py +0 -0
  79. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/_base_object.py +0 -0
  80. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/archive.py +0 -0
  81. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/blog.py +0 -0
  82. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/content_managers/__init__.py +0 -0
  83. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/engine.py +0 -0
  84. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/extras/__init__.py +0 -0
  85. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/feeds.py +0 -0
  86. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/hookspecs.py +0 -0
  87. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/links.py +0 -0
  88. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/parsers/markdown.py +0 -0
  89. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/plugins.py +0 -0
  90. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/py.typed +0 -0
  91. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/__init__.py +0 -0
  92. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/archive.html +0 -0
  93. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/base.html +0 -0
  94. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/base_collection_path.md +0 -0
  95. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/base_templates/_archive.html +0 -0
  96. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/base_templates/_base.html +0 -0
  97. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/base_templates/_page.html +0 -0
  98. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/components/footer.html +0 -0
  99. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/components/page_title.html +0 -0
  100. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/page.html +0 -0
  101. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/rss2.0.xml +0 -0
  102. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/rss2.0_items.xml +0 -0
  103. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/sitemap.xml +0 -0
  104. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/sitemap_item.xml +0 -0
  105. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/site_map.py +0 -0
  106. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/themes.py +0 -0
  107. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine/utils/__init__.py +0 -0
  108. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine.egg-info/dependency_links.txt +0 -0
  109. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine.egg-info/requires.txt +0 -0
  110. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/src/render_engine.egg-info/top_level.txt +0 -0
  111. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/tests/conftest.py +0 -0
  112. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/tests/test_archive.py +0 -0
  113. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/tests/test_base_object.py +0 -0
  114. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/tests/test_blog.py +0 -0
  115. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/tests/test_collections.py +0 -0
  116. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/tests/test_engine.py +0 -0
  117. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/tests/test_feeds/conftest_feed.py +0 -0
  118. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/tests/test_feeds/test_feeds.py +0 -0
  119. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/tests/test_page.py +0 -0
  120. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/tests/test_parsers_remove_2024_3_1.py +0 -0
  121. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/tests/test_site.py +0 -0
  122. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/tests/test_site_map.py +0 -0
  123. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/tests/test_theme_manager.py +0 -0
  124. {render_engine-2026.1.1a1 → render_engine-2026.1.1a2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: render_engine
3
- Version: 2026.1.1a1
3
+ Version: 2026.1.1a2
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/
@@ -65,3 +65,21 @@ prior to committing the entry to the datastore.
65
65
 
66
66
  !!! Note
67
67
  Not every `ContentManager` actually needs all the arguments that are passed.
68
+
69
+ ### `find_entry`
70
+
71
+ The `find_entry` method is used to find a specific page in a `Collection`. It will search through all the pages
72
+ known to the `ContentManager` until it finds one that has all the attributes specified. Even if multiple entries
73
+ would satisfy the criteria, only the first found will be returned.
74
+
75
+ ### `update_entry`
76
+
77
+ The `update_entry` is used to update an existing entry in a `ContentManager`.
78
+
79
+ Parameters:
80
+
81
+ ```python
82
+ page: Page # The Page to update
83
+ content: str = None # Updated content
84
+ **kwargs: dict # The other attributes for the updated page
85
+ ```
@@ -230,7 +230,7 @@ typing-extensions==4.15.0
230
230
  # typer
231
231
  tzdata==2025.3
232
232
  # via render_engine (pyproject.toml)
233
- urllib3==2.6.2
233
+ urllib3==2.6.3
234
234
  # via requests
235
235
  virtualenv==20.35.4
236
236
  # via pre-commit
@@ -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.1.1a1'
32
- __version_tuple__ = version_tuple = (2026, 1, 1, 'a1')
31
+ __version__ = version = '2026.1.1a2'
32
+ __version_tuple__ = version_tuple = (2026, 1, 1, 'a2')
33
33
 
34
- __commit_id__ = commit_id = 'gb307476d1'
34
+ __commit_id__ = commit_id = 'g6c6c29a4d'
@@ -12,10 +12,9 @@ from more_itertools import batched
12
12
  from render_engine_parser import BasePageParser
13
13
  from slugify import slugify
14
14
 
15
- from render_engine.content_managers import ContentManager, FileContentManager
16
-
17
15
  from ._base_object import BaseObject
18
16
  from .archive import Archive
17
+ from .content_managers import ContentManager, FileContentManager
19
18
  from .feeds import RSSFeed
20
19
  from .page import Page
21
20
  from .plugins import PluginManager
@@ -247,6 +246,9 @@ class Collection(BaseObject):
247
246
  def __iter__(self):
248
247
  yield from self.content_manager
249
248
 
249
+ def __len__(self):
250
+ return len(self.content_manager)
251
+
250
252
  @property
251
253
  def all_content(self) -> Generator:
252
254
  """All of the content that is associated with the Collection including Pages, Feed, and Archives"""
@@ -292,8 +294,7 @@ class Collection(BaseObject):
292
294
  entry.plugin_manager = copy.deepcopy(self.plugin_manager)
293
295
 
294
296
  entry.site = self.site
295
- for route in entry.routes:
296
- entry.render(route, self.site.theme_manager)
297
+ entry.render(self.site.theme_manager)
297
298
 
298
299
  def render(self) -> None:
299
300
  """Iterate through Pages and Check for Archives and Feeds"""
@@ -19,4 +19,21 @@ class ContentManager(ABC):
19
19
  @abstractmethod
20
20
  def create_entry(self, filepath: Path = None, editor: str = None, metadata: dict = None, content: str = None):
21
21
  """Create a new entry"""
22
- ...
22
+ pass
23
+
24
+ def find_entry(self, **kwargs):
25
+ """
26
+ Find an entry
27
+
28
+ :param kwargs: List of attributes to search by
29
+ :return: Page if it was found otherwise None
30
+ """
31
+ for page in self:
32
+ if all(getattr(page, attr, None) == value for attr, value in kwargs.items()):
33
+ return page
34
+ return None
35
+
36
+ @abstractmethod
37
+ def update_entry(self, *, page, **kwargs):
38
+ """Update an entry"""
39
+ pass
@@ -36,8 +36,16 @@ 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
+
39
42
  def create_entry(
40
- self, filepath: Path = None, editor: str = None, metadata: dict = None, content: str = None
43
+ self,
44
+ filepath: Path = None,
45
+ editor: str = None,
46
+ metadata: dict = None,
47
+ content: str = None,
48
+ update: bool = False,
41
49
  ) -> str:
42
50
  """
43
51
  Create a new entry for the Collection
@@ -46,12 +54,33 @@ class FileContentManager(ContentManager):
46
54
  :param editor: Editor to open to edit the entry.
47
55
  :param content: The content for the entry
48
56
  :param metadata: Metadata for the new entry
57
+ :param update: Allow overwriting the existing file
49
58
  """
50
59
  if not filepath:
51
60
  raise ValueError("filepath needs to be specified.")
52
61
 
62
+ if not update and filepath.exists():
63
+ raise RuntimeError(f"File at {filepath} exists and update is disabled.")
64
+
53
65
  parsed_content = self.collection.Parser.create_entry(content=content, **metadata)
54
66
  filepath.write_text(parsed_content)
55
67
  if editor:
56
68
  subprocess.run([editor, filepath])
57
69
  return f"New entry created at {filepath} ."
70
+
71
+ def update_entry(self, page, *, content: str = None, **kwargs) -> str:
72
+ """
73
+ Update an entry
74
+
75
+ :param page: Page object to update
76
+ :param content: Content for the updated page
77
+ :param kwargs: Attributes to be included in the updated page
78
+ :return: String indicating that the page was updated.
79
+ """
80
+ self.create_entry(filepath=page.content_path, metadata=kwargs, content=content, update=True)
81
+ if self._pages:
82
+ self._pages = [
83
+ existing_page for existing_page in self._pages if page.content_path != existing_page.content_path
84
+ ]
85
+ self._pages.append(self.collection.get_page(page.content_path))
86
+ return f"Entry at {page.content_path} updated."
@@ -149,20 +149,23 @@ class BasePage(BaseObject):
149
149
  def __repr__(self) -> str:
150
150
  return f"<Page: {self._title}>"
151
151
 
152
- def render(self, route: str | Path, theme_manager: ThemeManager) -> int:
152
+ def render(self, theme_manager: ThemeManager) -> int:
153
153
  """Render the page to the file system"""
154
- path = Path(self.site.output_path) / Path(route) / Path(self.path_name)
155
- path.parent.mkdir(parents=True, exist_ok=True)
156
- settings = dict()
157
- if (pm := getattr(self, "plugin_manager", None)) and pm is not None:
158
- settings = {**self.site.plugin_manager.plugin_settings, "route": route}
159
- pm.hook.render_content(page=self, settings=settings, site=self.site)
160
- self.rendered_content = self._render_content(theme_manager.engine)
161
- # pass the route to the plugin settings
162
- if pm is not None:
163
- pm.hook.post_render_content(page=self.__class__, settings=settings, site=self.site)
164
-
165
- return path.write_text(self.rendered_content)
154
+ rc = 0
155
+ for route in self.routes:
156
+ path = Path(self.site.output_path) / Path(route) / Path(self.path_name)
157
+ path.parent.mkdir(parents=True, exist_ok=True)
158
+ settings = dict()
159
+ if (pm := getattr(self, "plugin_manager", None)) and pm is not None:
160
+ settings = {**self.site.plugin_manager.plugin_settings, "route": route}
161
+ pm.hook.render_content(page=self, settings=settings, site=self.site)
162
+ self.rendered_content = self._render_content(theme_manager.engine)
163
+ # pass the route to the plugin settings
164
+ if pm is not None:
165
+ pm.hook.post_render_content(page=self.__class__, settings=settings, site=self.site)
166
+
167
+ rc += path.write_text(self.rendered_content)
168
+ return rc
166
169
 
167
170
 
168
171
  class Page(BasePage):
@@ -242,6 +245,8 @@ class Page(BasePage):
242
245
  for key, val in self.metadata.items():
243
246
  setattr(self, key.lower(), val)
244
247
 
248
+ self.content_path = content_path
249
+
245
250
  @property
246
251
  def _content(self) -> Any:
247
252
  """
@@ -306,14 +306,11 @@ class Site:
306
306
  args = []
307
307
  match entry:
308
308
  case Page():
309
- for route in entry.routes:
310
- progress.update(
311
- task_add_route,
312
- description=f"[blue]Adding[gold]Route: [blue]{entry._slug}",
313
- )
314
-
315
- # self._render_output(route, entry)
316
- args = [route, self.theme_manager]
309
+ progress.update(
310
+ task_add_route,
311
+ description=f"[blue]Adding[gold]Route: [blue]{entry._slug}",
312
+ )
313
+ args = [self.theme_manager]
317
314
  case Collection():
318
315
  progress.update(
319
316
  task_add_route,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: render_engine
3
- Version: 2026.1.1a1
3
+ Version: 2026.1.1a2
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/
@@ -110,6 +110,7 @@ tests/test_base_object.py
110
110
  tests/test_blog.py
111
111
  tests/test_collections.py
112
112
  tests/test_engine.py
113
+ tests/test_file_content_manager.py
113
114
  tests/test_page.py
114
115
  tests/test_parsers_remove_2024_3_1.py
115
116
  tests/test_plugins.py
@@ -0,0 +1,78 @@
1
+ from pathlib import Path
2
+
3
+ from render_engine import Collection
4
+ from render_engine.content_managers import FileContentManager
5
+
6
+
7
+ def test_find_entry(tmp_path):
8
+ content_path = Path(tmp_path, "test-collection")
9
+ content_path.mkdir(parents=True)
10
+ filepath = content_path / "content.md"
11
+ content = "Test new content"
12
+ filepath.write_text(content)
13
+
14
+ class TestCollection(Collection):
15
+ content_path = Path(tmp_path, "test-collection")
16
+ ContentManager = FileContentManager
17
+
18
+ collection = TestCollection()
19
+ page = collection.content_manager.find_entry(content_path=filepath)
20
+ assert page is not None
21
+ assert page.content_path == filepath
22
+ assert page.content == content
23
+
24
+
25
+ def test_find_no_valid_entry(tmp_path):
26
+ content_path = Path(tmp_path, "test-collection")
27
+ content_path.mkdir(parents=True)
28
+ filepath = content_path / "content.md"
29
+ content = "Test new content"
30
+ filepath.write_text(content)
31
+
32
+ class TestCollection(Collection):
33
+ content_path = Path(tmp_path, "test-collection")
34
+ ContentManager = FileContentManager
35
+
36
+ collection = TestCollection()
37
+ page = collection.content_manager.find_entry(content_path=filepath, test_attr="Test")
38
+ assert page is None
39
+ page = collection.content_manager.find_entry(content_path=filepath)
40
+ assert page is not None
41
+
42
+
43
+ def test_find_entry_no_entries(tmp_path):
44
+ content_path = Path(tmp_path, "test-collection")
45
+ content_path.mkdir(parents=True)
46
+ filepath = content_path / "content.md"
47
+
48
+ class TestCollection(Collection):
49
+ content_path = Path(tmp_path, "test-collection")
50
+ ContentManager = FileContentManager
51
+
52
+ collection = TestCollection()
53
+ page = collection.content_manager.find_entry(content_path=filepath)
54
+ assert page is None
55
+
56
+
57
+ def test_update_entry(tmp_path):
58
+ content_path = Path(tmp_path, "test-collection")
59
+ content_path.mkdir(parents=True)
60
+ filepath = content_path / "content.md"
61
+ content = "Test new content"
62
+ updated = "Test updated content"
63
+ filepath.write_text(content)
64
+
65
+ class TestCollection(Collection):
66
+ content_path = Path(tmp_path, "test-collection")
67
+ ContentManager = FileContentManager
68
+
69
+ collection = TestCollection()
70
+ assert len(collection.content_manager) == 1
71
+ page = collection.content_manager.find_entry(content_path=filepath)
72
+ assert filepath.read_text() == page.content
73
+ collection.content_manager.update_entry(page, content=updated, test_attr="Test")
74
+ assert filepath.read_text() == f"---\ntest_attr: Test\n---\n\n{updated}"
75
+ page = collection.content_manager.find_entry(content_path=filepath)
76
+ assert page.content == updated
77
+ assert page.test_attr == "Test"
78
+ assert len(collection.content_manager) == 1
@@ -254,7 +254,7 @@ class TestPlugin:
254
254
  print("PreBuild")
255
255
  prebuild = PreBuild()
256
256
  prebuild.site = site
257
- prebuild.render("./", site.theme_manager)
257
+ prebuild.render(site.theme_manager)
258
258
 
259
259
  @staticmethod
260
260
  @hook_impl
@@ -266,7 +266,7 @@ class TestPlugin:
266
266
  print("PostBuild")
267
267
  postbuild = PostBuild()
268
268
  postbuild.site = site
269
- postbuild.render("./", site.theme_manager)
269
+ postbuild.render(site.theme_manager)
270
270
 
271
271
  @staticmethod
272
272
  @hook_impl
@@ -278,7 +278,7 @@ class TestPlugin:
278
278
  print("RenderContent")
279
279
  renddercontent = RenderContent()
280
280
  renddercontent.site = site
281
- renddercontent.render("./", site.theme_manager)
281
+ renddercontent.render(site.theme_manager)
282
282
 
283
283
  @staticmethod
284
284
  @hook_impl
@@ -290,7 +290,7 @@ class TestPlugin:
290
290
  print("PostRenderContent")
291
291
  postrendercontent = PostRenderContent()
292
292
  postrendercontent.site = site
293
- postrendercontent.render("./", site.theme_manager)
293
+ postrendercontent.render(site.theme_manager)
294
294
 
295
295
  @staticmethod
296
296
  @hook_impl
@@ -303,7 +303,7 @@ class TestPlugin:
303
303
  print("PreBuildCollection")
304
304
  prebuildcollection = PreBuildCollection()
305
305
  prebuildcollection.site = site
306
- prebuildcollection.render("./", site.theme_manager)
306
+ prebuildcollection.render(site.theme_manager)
307
307
 
308
308
  @staticmethod
309
309
  @hook_impl
@@ -315,7 +315,7 @@ class TestPlugin:
315
315
  print("PostBuildCollection")
316
316
  postbuildcollection = PostBuildCollection()
317
317
  postbuildcollection.site = site
318
- postbuildcollection.render("./", site.theme_manager)
318
+ postbuildcollection.render(site.theme_manager)
319
319
 
320
320
 
321
321
  @pytest.fixture(scope="function")
@@ -33,7 +33,7 @@ def test_base_html_body_class(theme_site):
33
33
 
34
34
  theme_site.register_themes(BodyClassTheme)
35
35
  theme_site.route_list["testpage"].site = theme_site
36
- theme_site.route_list["testpage"].render("./", theme_site.theme_manager)
36
+ theme_site.route_list["testpage"].render(theme_site.theme_manager)
37
37
 
38
38
  assert '<body class="my-class">' in (theme_site.output_path / "testpage.html").read_text()
39
39
 
@@ -51,7 +51,7 @@ def test_base_html_head_include(theme_site):
51
51
  theme_site.register_themes(HeadIncludeTheme)
52
52
  theme_site.load_themes()
53
53
  theme_site.route_list["testpage"].site = theme_site
54
- theme_site.route_list["testpage"].render("./", theme_site.theme_manager)
54
+ theme_site.route_list["testpage"].render(theme_site.theme_manager)
55
55
 
56
56
  assert "<script>console.log('test')</script>" in (theme_site.output_path / "testpage.html").read_text()
57
57