puppygirl-py/puppygirl/renderer.py
2025-10-06 16:11:34 -04:00

59 lines
2 KiB
Python

from dataclasses import InitVar, dataclass, field
from importlib import resources
from typing import TYPE_CHECKING, Iterable, Protocol, runtime_checkable
from puppygirl.elements import IdAttr, TemplateName
from puppygirl.elements.shadow_root import Node
from puppygirl.puppytype import Templates
if TYPE_CHECKING:
from bs4 import BeautifulSoup
from puppygirl import Puppygirl
@runtime_checkable
class Renderable(Protocol):
def render(self, tree: "BeautifulSoup") -> "BeautifulSoup":
pass
class Renderer(Protocol):
def render(self, puppygirl: "Puppygirl", tree: "BeautifulSoup") -> "BeautifulSoup":
pass
@dataclass
class ClientSideRenderer(Renderer):
path: InitVar[str] = 'clientside.js'
injected_js: str = field(init = False)
def __post_init__(self, path: str):
with resources.open_text('puppygirl.assets', path) as f:
self.injected_js = f.read()
def render(self, puppygirl: "Puppygirl", tree: "BeautifulSoup") -> "BeautifulSoup":
head = tree.find("head")
script = tree.new_tag("script", attrs={"type": "text/javascript"})
script.append(self.injected_js)
head.append(script)
return tree
class ServerSideRenderer(Renderer):
def _find_local_templates(puppygirl: "Puppygirl", tree: "BeautifulSoup") -> "Templates":
templates = tree.find_all(TemplateName)
templates = filter(lambda t: t.has_attr(IdAttr), templates)
return puppygirl.__class__._create_template_dict(templates)
def render(self, puppygirl: "Puppygirl", tree: "BeautifulSoup") -> "BeautifulSoup":
templates = ServerSideRenderer._find_local_templates(puppygirl, tree) | puppygirl.templates
for element in puppygirl.elements:
if hasattr(element, "name"):
for tag in tree.find_all(element.name):
new_tag = element.render(Node(tag).clone(), templates)
if isinstance(new_tag, Iterable):
tag.extend(new_tag)
tag.unwrap()
else:
tag.replace_with(new_tag.value)
return tree