94 lines
2.7 KiB
Python
94 lines
2.7 KiB
Python
from multiprocessing import Pool
|
|
from dataclasses import InitVar, dataclass, field
|
|
from importlib import resources
|
|
from typing import TYPE_CHECKING, Any, Iterable, Protocol, runtime_checkable
|
|
|
|
from bs4 import Tag
|
|
|
|
from puppygirl.elements import IdAttr, TemplateName
|
|
from puppygirl.elements.shadow_root import Node, SingleNode, Template
|
|
from puppygirl.puppytype import RenderableElement, Templates
|
|
|
|
if TYPE_CHECKING:
|
|
from bs4 import BeautifulSoup
|
|
from puppygirl import Puppygirl
|
|
|
|
class Query:
|
|
_args: Iterable[Any]
|
|
_kwargs: dict[str, Any]
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self._args = args
|
|
self._kwargs = kwargs
|
|
|
|
def query(self, tree: "BeautifulSoup") -> Iterable[Tag]:
|
|
return tree.find_all(*self._args, **self._kwargs)
|
|
|
|
@dataclass
|
|
class RenderState:
|
|
templates: "Templates"
|
|
|
|
@runtime_checkable
|
|
class Renderable(Protocol):
|
|
def query(self) -> Query:
|
|
return Query()
|
|
|
|
def render(self, node: "Node", state: RenderState) -> "RenderableElement":
|
|
return node
|
|
|
|
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
|
|
state = RenderState(templates)
|
|
|
|
for element in puppygirl.elements:
|
|
if hasattr(element, "name"):
|
|
query = element.query()
|
|
tags = query.query(tree)
|
|
tags.reverse()
|
|
|
|
for tag in tags:
|
|
node = SingleNode(tag)
|
|
new_tag = element.render(
|
|
node,
|
|
state
|
|
)
|
|
|
|
if new_tag is None or new_tag.value == tag:
|
|
continue
|
|
else:
|
|
node.replace(new_tag)
|
|
# elif isinstance(new_tag, Iterable):
|
|
# tag.extend(new_tag.value)
|
|
# tag.unwrap()
|
|
# else:
|
|
# tag.replace_with(new_tag.value)
|
|
|
|
return tree
|
|
|