issue with nested templates
This commit is contained in:
parent
3deafd4d97
commit
a2d0163949
3 changed files with 91 additions and 29 deletions
|
@ -1,14 +1,15 @@
|
|||
from dataclasses import dataclass
|
||||
import re
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .constants import TemplateAttr
|
||||
from .shadow_root import ShadowRootMode
|
||||
from ..renderer import Query, Renderable
|
||||
from .shadow_root import ShadowRootMode, SingleNode
|
||||
from ..renderer import Query, RenderState, Renderable
|
||||
from .shadow_root import Node
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import Puppygirl
|
||||
from .shadow_root import Node
|
||||
from ..puppytype import RenderableElement, Templates
|
||||
from ..puppytype import RenderableElement
|
||||
|
||||
@dataclass
|
||||
class PuppygirlHost(Renderable):
|
||||
|
@ -19,16 +20,40 @@ class PuppygirlHost(Renderable):
|
|||
return "pg-host"
|
||||
|
||||
def query(self) -> Query:
|
||||
return Query(self.name, attrs={ "template": True })
|
||||
return Query(self.name, attrs={ "template": True }, recursive=True)
|
||||
|
||||
def render(self, node: "Node", templates: "Templates") -> "RenderableElement":
|
||||
template = templates[node[TemplateAttr]]
|
||||
def render(self, node: "Node", state: "RenderState") -> "RenderableElement":
|
||||
node = node.clone()
|
||||
|
||||
template = state.templates.get(node.get_attr(TemplateAttr)).clone()
|
||||
|
||||
for tag in self.query().query(template):
|
||||
result = self.render(SingleNode(tag), state)
|
||||
Node.from_tag(tag).replace(result)
|
||||
|
||||
shadow_root = node.attach_shadow(ShadowRootMode.Open)
|
||||
shadow_root.append_child(template.clone())
|
||||
|
||||
return node
|
||||
|
||||
def get_exported_name(part: str, mapping: str) -> str | None:
|
||||
mapping = mapping.strip()
|
||||
map_len = len(mapping)
|
||||
|
||||
if map_len == 0: return None
|
||||
|
||||
index = mapping.index(part)
|
||||
end = index + len(part)
|
||||
|
||||
if end >= map_len or mapping[end] == ":":
|
||||
return part
|
||||
else:
|
||||
try:
|
||||
map_end = mapping.index(",", end)
|
||||
return mapping[end + 1:map_end]
|
||||
except:
|
||||
return mapping[end + 1:]
|
||||
|
||||
@dataclass
|
||||
class PuppygirlPart(Renderable):
|
||||
puppygirl: "Puppygirl"
|
||||
|
@ -40,14 +65,20 @@ class PuppygirlPart(Renderable):
|
|||
def query(self) -> Query:
|
||||
return Query(self.name, attrs={ "for": True })
|
||||
|
||||
def render(self, node: "Node", templates: "Templates") -> "RenderableElement":
|
||||
def replace_part(self, node: "Node", part_name: str):
|
||||
template = node.parent.find("template", recursive=False)
|
||||
|
||||
if template is None:
|
||||
return node
|
||||
|
||||
part_name = node["for"]
|
||||
parts = template.find_all(attrs={ "part": part_name })
|
||||
parts = template(part=part_name, recusive=False)
|
||||
exports = template(exportparts=re.compile(part_name), recursive=False)
|
||||
|
||||
for export in exports:
|
||||
mapping = get_exported_name(part_name, export["exportparts"])
|
||||
if mapping is None: continue
|
||||
|
||||
self.replace_part(SingleNode(export), part_name)
|
||||
|
||||
attrs = {
|
||||
attr: value
|
||||
|
@ -58,8 +89,14 @@ class PuppygirlPart(Renderable):
|
|||
for attr, value in attrs.items():
|
||||
for part in parts:
|
||||
part[attr] = value
|
||||
|
||||
|
||||
def render(self, node: "Node", state: "RenderState") -> "RenderableElement":
|
||||
part_name = node["for"]
|
||||
|
||||
self.replace_part(node, part_name)
|
||||
|
||||
return []
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -22,12 +22,16 @@ class Node(Clonable):
|
|||
def from_path(path: str) -> Self:
|
||||
with open(path, "r") as f:
|
||||
soup = BeautifulSoup(f.read(), 'html.parser')
|
||||
nodes = soup.find_all()
|
||||
return Node.from_tag(soup)
|
||||
|
||||
match len(nodes):
|
||||
case 0: return EmptyNode()
|
||||
case 1: return SingleNode(nodes[0])
|
||||
case _: return NodeCollection(soup)
|
||||
def from_tag(tag: Tag | Iterable[Tag] | BeautifulSoup) -> Self:
|
||||
if isinstance(tag, Tag):
|
||||
return SingleNode(tag)
|
||||
|
||||
match len(tag):
|
||||
case 0: return EmptyNode()
|
||||
case 1: return SingleNode(tag[0])
|
||||
case _: return NodeCollection(tag)
|
||||
|
||||
def attach_shadow(self, shadow_root_mode: ShadowRootMode, clonable: Optional[bool] = None, delegates_focus: Optional[bool] = None, serializable: Optional[bool] = None):
|
||||
return ShadowRoot(
|
||||
|
@ -50,9 +54,16 @@ class Node(Clonable):
|
|||
def insert(self, *args, **kwargs):
|
||||
return self.value.insert(*args, **kwargs)
|
||||
|
||||
def get_attr(self, attr: str) -> Optional[str]:
|
||||
return self.value.get(attr)
|
||||
|
||||
def has_attr(self, attr: str) -> bool:
|
||||
return self.value.has_attr(attr)
|
||||
|
||||
def replace(self, node: "Node | Iterable[Node]"):
|
||||
self.value.insert_before(node.value)
|
||||
self.value.decompose()
|
||||
|
||||
def prettify(self) -> str:
|
||||
return self.value.prettify()
|
||||
|
||||
|
@ -65,6 +76,9 @@ class EmptyNode(Node):
|
|||
class NodeCollection(Node):
|
||||
nodes: Iterable[Tag]
|
||||
|
||||
def __iter__(self):
|
||||
return self.nodes
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self.nodes
|
||||
|
@ -100,7 +114,7 @@ class SingleNode(Node):
|
|||
return SingleNode(copy(self.value))
|
||||
|
||||
@dataclass
|
||||
class Template(Clonable):
|
||||
class Template(Node):
|
||||
_value: Node
|
||||
|
||||
@property
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
from multiprocessing import Pool
|
||||
from dataclasses import InitVar, dataclass, field
|
||||
from importlib import resources
|
||||
from typing import TYPE_CHECKING, Any, Iterable, Protocol, Self, runtime_checkable
|
||||
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
|
||||
from puppygirl.puppytype import Templates
|
||||
from puppygirl.elements.shadow_root import Node, SingleNode, Template
|
||||
from puppygirl.puppytype import RenderableElement, Templates
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bs4 import BeautifulSoup
|
||||
|
@ -23,13 +24,17 @@ class Query:
|
|||
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, tree: "BeautifulSoup") -> "BeautifulSoup":
|
||||
return tree
|
||||
def render(self, node: "Node", state: RenderState) -> "RenderableElement":
|
||||
return node
|
||||
|
||||
class Renderer(Protocol):
|
||||
def render(self, puppygirl: "Puppygirl", tree: "BeautifulSoup") -> "BeautifulSoup":
|
||||
|
@ -60,24 +65,30 @@ class ServerSideRenderer(Renderer):
|
|||
|
||||
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(
|
||||
SingleNode(tag),
|
||||
# SingleNode(tag).clone(),
|
||||
templates
|
||||
node,
|
||||
state
|
||||
)
|
||||
|
||||
if isinstance(new_tag, Iterable):
|
||||
tag.extend(new_tag)
|
||||
tag.unwrap()
|
||||
if new_tag is None or new_tag.value == tag:
|
||||
continue
|
||||
else:
|
||||
tag.replace_with(new_tag.value)
|
||||
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
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue