Skip to content

Commit

Permalink
Merge pull request #429 from render-engine/kjaymiller/Plugins-from-Co…
Browse files Browse the repository at this point in the history
…llection-and-Page-not-from-Site

Creates Plugin Manager
  • Loading branch information
kjaymiller committed Dec 2, 2023
2 parents ded3bae + 8ff185f commit b6039a2
Show file tree
Hide file tree
Showing 21 changed files with 259 additions and 239 deletions.
1 change: 1 addition & 0 deletions output/static/test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test
16 changes: 1 addition & 15 deletions src/render_engine/_base_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

from slugify import slugify

from .hookspecs import register_plugins


class BaseObject:
"""
Expand Down Expand Up @@ -39,9 +37,7 @@ def extension(self) -> str:
@extension.setter
def extension(self, extension: str) -> None:
"""Ensures consistency on extension"""
if not extension.startswith("."):
self._extension = f".{extension}"
self._extension = extension
self._extension = f".{extension.lstrip('.')}"

@property
def path_name(self) -> str:
Expand Down Expand Up @@ -74,13 +70,3 @@ def to_dict(self):
base_dict[key] = value

return base_dict

def register_plugins(self, plugins):
"""Creates the plugin manager and registers plugins"""

if getattr("self", "plugins", None):
self.plugins.extend(plugins)
else:
self.plugins = plugins

self._pm = register_plugins(self.plugins)
12 changes: 8 additions & 4 deletions src/render_engine/archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import jinja2

from render_engine.plugins import PluginManager

from .page import BasePage


Expand Down Expand Up @@ -32,13 +34,15 @@ def __init__(
routes: list[str | pathlib.Path],
archive_index: int = 0,
num_archive_pages: int = 1,
plugin_manager: PluginManager | None = None,
) -> None:
super().__init__()
self.pages = pages
self.template = template
self.routes = routes
self.archive_index = archive_index
self.title = title

if archive_index:
self.slug = f"{self._slug}{archive_index}"
self.pages = pages
self.plugin_manager = plugin_manager
self.routes = routes
self.template = template
self.title = title
3 changes: 3 additions & 0 deletions src/render_engine/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from .page import Page
from .parsers import BasePageParser
from .parsers.markdown import MarkdownPageParser
from .plugins import PluginManager


class Collection(BaseObject):
Expand Down Expand Up @@ -72,6 +73,7 @@ class BasicCollection(Collection):
sort_reverse: bool = False
template: str | None
plugins: list[typing.Callable] | None
plugin_manager: PluginManager | None

def __init__(
self,
Expand Down Expand Up @@ -164,6 +166,7 @@ def archives(self) -> typing.Generator[Archive, None, None]:
routes=self.routes,
archive_index=index,
num_archive_pages=num_archive_pages,
plugin_manager=getattr(self, "plugin_manager", None),
)

@property
Expand Down
7 changes: 7 additions & 0 deletions src/render_engine/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
Environment,
FileSystemLoader,
PackageLoader,
PrefixLoader,
pass_environment,
select_autoescape,
)
Expand All @@ -15,7 +16,13 @@

render_engine_templates_loader = ChoiceLoader(
[
# Newly Registered Themes 'templates folder' goes here
FileSystemLoader("templates"),
PrefixLoader(
{
# "prefix": theme.loader
}
),
PackageLoader("render_engine", "render_engine_templates"),
]
)
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
10 changes: 0 additions & 10 deletions src/render_engine/hookspecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,6 @@
hook_spec = pluggy.HookspecMarker(project_name=_PROJECT_NAME)


def register_plugins(plugins):
"""Register the plugins with the plugin manager"""
pm = pluggy.PluginManager(project_name=_PROJECT_NAME)
pm.add_hookspecs(SiteSpecs)

for plugin in plugins:
pm.register(plugin)
return pm


class SiteSpecs:
"""Plugin hook specifications for the Site class"""

Expand Down
5 changes: 4 additions & 1 deletion src/render_engine/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ def _render_content(self, engine: jinja2.Environment | None = None, **kwargs) ->
return self._content

else:
raise ValueError("The returned content attribute must be a string.")
raise ValueError(
f"Error rendering {self}. \
The returned content attribute must be a string. Got {type(self._content)}"
)

except AttributeError:
raise AttributeError(
Expand Down
17 changes: 17 additions & 0 deletions src/render_engine/plugins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import pluggy

from .hookspecs import _PROJECT_NAME, SiteSpecs


class PluginManager:
def __init__(self):
self._pm = pluggy.PluginManager(project_name=_PROJECT_NAME)
self._pm.add_hookspecs(SiteSpecs)

def register_plugin(self, plugin) -> None:
"""Register a plugin with the site"""
self._pm.register(plugin)

@property
def plugins(self):
return self._pm.get_plugins()
125 changes: 67 additions & 58 deletions src/render_engine/site.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,27 @@
import copy
import logging
import pathlib
import typing
from collections import defaultdict

import pluggy
from jinja2 import Environment, FileSystemLoader
from jinja2 import FileSystemLoader, PrefixLoader
from rich.progress import Progress

from .collection import Collection
from .engine import engine
from .hookspecs import _PROJECT_NAME, SiteSpecs
from .page import Page
from .plugins import PluginManager
from .themes import Theme, ThemeManager


class Site(ThemeManager):
class Site:
"""
The site stores your pages and collections to be rendered.
Attributes:
engine: Jinja2 Environment used to render pages
output_path:
path to write rendered content
partial:
if True, only render pages that have been modified. Uses gitPython to check for changes.
plugins:
list of plugins that will be loaded and passed into each object
static_paths:
list of paths for static folders. This will get copied to the output folder. Folders are recursive.
site_vars:
Expand All @@ -34,66 +30,70 @@ class Site(ThemeManager):
settings that will be passed into pages and collections but not into templates
"""

_pm: pluggy.PluginManager
partial: bool = False
site_settings: dict = {"plugins": {}}
site_vars: dict = {
"SITE_TITLE": "Untitled Site",
"SITE_URL": "http://localhost:8000/",
"DATETIME_FORMAT": "%d %b %Y %H:%M %Z",
"head": set(),
"theme": {},
}
engine: Environment = engine
_output_path: str = "output"
static_paths: set = {"static"}
plugin_settings: dict = {"plugins": defaultdict(dict)}
template_path: str = "templates"

def __init__(
self,
) -> None:
self.plugin_manager = PluginManager()
self.theme_manager = ThemeManager(
engine=engine,
output_path=self._output_path,
static_paths=self.static_paths,
)
self.route_list = dict()
self.site_settings = dict()
self.subcollections = defaultdict(lambda: {"pages": []})
self.engine.globals.update(self.site_vars)
self.theme_manager.engine.globals.update(self.site_vars)

if self.template_path:
self.engine.loader.loaders.insert(0, FileSystemLoader(self.template_path))
self.theme_manager.engine.loader.loaders.insert(0, FileSystemLoader(self.template_path))

@property
def output_path(self):
return self.theme_manager.output_path

# Manage Plugins
self._pm = pluggy.PluginManager(project_name=_PROJECT_NAME)
self._pm.add_hookspecs(SiteSpecs)
@output_path.setter
def output_path(self, output_path: str):
self.theme_manager.output_path = output_path

def update_site_vars(self, **kwargs) -> None:
self.site_vars.update(**kwargs)
self.engine.globals.update(self.site_vars)

def register_plugins(self, *plugins, **settings: dict[str, typing.Any]) -> None:
"""Register plugins with the site
parameters:
plugins: list of plugins to register
settings: settings to pass into the plugins
settings keys are the plugin names as strings.
"""
self.theme_manager.engine.globals.update(self.site_vars)

def register_plugins(self, *plugins):
for plugin in plugins:
self._pm.register(plugin)
self.site_settings["plugins"][plugin.__name__] = getattr(plugin, "default_settings", {})

self._pm.hook.add_default_settings(
site=self,
custom_settings=settings,
)
self.site_settings["plugins"].update(**settings)

@property
def plugins(self):
return self._pm.get_plugins()
self.plugin_manager.register_plugin(plugin)
if plugin_settings := self.plugin_settings.get("plugins"):
plugin.default_settings = plugin_settings

def register_theme(self, theme: Theme):
"""Overrides the ThemeManager register_theme method to add plugins to the site"""
super().register_theme(theme)
self.theme_manager.register_theme(theme)

if theme.plugins:
self.register_plugins(*theme.plugins)
self.plugin_manager._pm.register_plugins(*theme.plugins)

def register_themes(self, *themes: Theme):
"""
Register multiple themes.
Args:
*themes: Theme objects to register
"""
for theme in themes:
self.register_theme(theme)

def update_theme_settings(self, **settings):
for key, value in settings.items():
Expand Down Expand Up @@ -125,17 +125,15 @@ class Posts(Collection):
```
"""
_Collection = Collection()
_Collection.plugin_manager = copy.deepcopy(self.plugin_manager)
self.register_themes(*getattr(_Collection, "required_themes", []))
plugins = [*self.plugins, *getattr(_Collection, "plugins", [])]

for plugin in getattr(_Collection, "plugins", []):
_Collection.plugin_manager._pm.register(plugin)

for plugin in getattr(_Collection, "ignore_plugins", []):
plugins.remove(plugin)
_Collection.register_plugins(plugins)
_Collection.plugin_manager._pm.unregister(plugin)

self._pm.hook.pre_build_collection(
collection=_Collection,
settings=self.site_settings.get("plugins", {}),
) # type: ignore
self.route_list[_Collection._slug] = _Collection
return _Collection

Expand Down Expand Up @@ -169,7 +167,10 @@ class About(Page):
page.title = page._title # Expose _title to the user through `title`

# copy the plugin manager, removing any plugins that the page has ignored
page._pm = self._pm
page._pm = copy.deepcopy(self.plugin_manager._pm)

for plugin in getattr(page, "plugins", []):
page._pm.register(plugin)

for plugin in getattr(page, "ignore_plugins", []):
page._pm.unregister(plugin)
Expand All @@ -181,10 +182,14 @@ def _render_output(self, route: str, page: Page):
path = pathlib.Path(self.output_path) / pathlib.Path(route) / pathlib.Path(page.path_name)
path.parent.mkdir(parents=True, exist_ok=True)
settings = {**self.site_settings.get("plugins", {}), **{"route": route}}
self._pm.hook.render_content(page=page, settings=settings)
page.rendered_content = page._render_content(engine=self.engine)

if hasattr(page, "plugin_manager"):
page.plugin_manager._pm.hook.render_content(page=page, settings=settings)
page.rendered_content = page._render_content(engine=self.theme_manager.engine)
# pass the route to the plugin settings
self._pm.hook.post_render_content(page=page.__class__, settings=settings, site=self)

if hasattr(page, "plugin_manager"):
page.plugin_manager._pm.hook.post_render_content(page=page.__class__, settings=settings, site=self)

return path.write_text(page.rendered_content)

Expand All @@ -207,6 +212,8 @@ def _render_full_collection(self, collection: Collection) -> None:
"""Iterate through Pages and Check for Collections and Feeds"""

for entry in collection:
entry._pm = copy.deepcopy(self.plugin_manager._pm)

for route in collection.routes:
self._render_output(route, entry)

Expand Down Expand Up @@ -236,22 +243,24 @@ def render(self) -> None:

with Progress() as progress:
pre_build_task = progress.add_task("Loading Pre-Build Plugins", total=1)
self._pm.hook.pre_build_site(site=self, settings=self.site_settings.get("plugins", {})) # type: ignore
print(self.plugin_manager._pm.hook)
self.plugin_manager._pm.hook.pre_build_site(site=self, settings=self.site_settings.get("plugins", {})) # type: ignore

self.engine.globals.update(self.site_vars)
self.theme_manager.engine.loader.loaders.insert(-2, PrefixLoader(self.theme_manager.prefix))
self.theme_manager.engine.globals.update(self.site_vars)
# Parse Route List
task_add_route = progress.add_task("[blue]Adding Routes", total=len(self.route_list))

self._render_static()
self.theme_manager._render_static()

self.engine.globals["site"] = self
self.engine.globals["routes"] = self.route_list
self.theme_manager.engine.globals["site"] = self
self.theme_manager.engine.globals["routes"] = self.route_list

for slug, entry in self.route_list.items():
progress.update(task_add_route, description=f"[blue]Adding[gold]Route: [blue]{slug}")
if isinstance(entry, Page):
if getattr(entry, "collection", None):
self._pm.hook.render_content(Page=entry, settings=self.site_settings.get("plugins", None))
Page._pm.hook.render_content(Page=entry, settings=self.site_settings.get("plugins", None))
for route in entry.routes:
progress.update(
task_add_route,
Expand All @@ -266,7 +275,7 @@ def render(self) -> None:
self._render_partial_collection(entry)

progress.add_task("Loading Post-Build Plugins", total=1)
self._pm.hook.post_build_site(
self.plugin_manager._pm.hook.post_build_site(
site=self,
settings=self.site_settings.get("plugins", {}),
)
Expand Down

0 comments on commit b6039a2

Please sign in to comment.