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
|
from dataclasses import dataclass
|
||||||
|
import re
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from .constants import TemplateAttr
|
from .constants import TemplateAttr
|
||||||
from .shadow_root import ShadowRootMode
|
from .shadow_root import ShadowRootMode, SingleNode
|
||||||
from ..renderer import Query, Renderable
|
from ..renderer import Query, RenderState, Renderable
|
||||||
|
from .shadow_root import Node
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .. import Puppygirl
|
from .. import Puppygirl
|
||||||
from .shadow_root import Node
|
from ..puppytype import RenderableElement
|
||||||
from ..puppytype import RenderableElement, Templates
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PuppygirlHost(Renderable):
|
class PuppygirlHost(Renderable):
|
||||||
|
@ -19,16 +20,40 @@ class PuppygirlHost(Renderable):
|
||||||
return "pg-host"
|
return "pg-host"
|
||||||
|
|
||||||
def query(self) -> Query:
|
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":
|
def render(self, node: "Node", state: "RenderState") -> "RenderableElement":
|
||||||
template = templates[node[TemplateAttr]]
|
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 = node.attach_shadow(ShadowRootMode.Open)
|
||||||
shadow_root.append_child(template.clone())
|
shadow_root.append_child(template.clone())
|
||||||
|
|
||||||
return node
|
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
|
@dataclass
|
||||||
class PuppygirlPart(Renderable):
|
class PuppygirlPart(Renderable):
|
||||||
puppygirl: "Puppygirl"
|
puppygirl: "Puppygirl"
|
||||||
|
@ -40,14 +65,20 @@ class PuppygirlPart(Renderable):
|
||||||
def query(self) -> Query:
|
def query(self) -> Query:
|
||||||
return Query(self.name, attrs={ "for": True })
|
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)
|
template = node.parent.find("template", recursive=False)
|
||||||
|
|
||||||
if template is None:
|
if template is None:
|
||||||
return node
|
return node
|
||||||
|
|
||||||
part_name = node["for"]
|
parts = template(part=part_name, recusive=False)
|
||||||
parts = template.find_all(attrs={ "part": part_name })
|
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 = {
|
attrs = {
|
||||||
attr: value
|
attr: value
|
||||||
|
@ -58,8 +89,14 @@ class PuppygirlPart(Renderable):
|
||||||
for attr, value in attrs.items():
|
for attr, value in attrs.items():
|
||||||
for part in parts:
|
for part in parts:
|
||||||
part[attr] = value
|
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:
|
def from_path(path: str) -> Self:
|
||||||
with open(path, "r") as f:
|
with open(path, "r") as f:
|
||||||
soup = BeautifulSoup(f.read(), 'html.parser')
|
soup = BeautifulSoup(f.read(), 'html.parser')
|
||||||
nodes = soup.find_all()
|
return Node.from_tag(soup)
|
||||||
|
|
||||||
match len(nodes):
|
def from_tag(tag: Tag | Iterable[Tag] | BeautifulSoup) -> Self:
|
||||||
case 0: return EmptyNode()
|
if isinstance(tag, Tag):
|
||||||
case 1: return SingleNode(nodes[0])
|
return SingleNode(tag)
|
||||||
case _: return NodeCollection(soup)
|
|
||||||
|
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):
|
def attach_shadow(self, shadow_root_mode: ShadowRootMode, clonable: Optional[bool] = None, delegates_focus: Optional[bool] = None, serializable: Optional[bool] = None):
|
||||||
return ShadowRoot(
|
return ShadowRoot(
|
||||||
|
@ -50,9 +54,16 @@ class Node(Clonable):
|
||||||
def insert(self, *args, **kwargs):
|
def insert(self, *args, **kwargs):
|
||||||
return self.value.insert(*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:
|
def has_attr(self, attr: str) -> bool:
|
||||||
return self.value.has_attr(attr)
|
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:
|
def prettify(self) -> str:
|
||||||
return self.value.prettify()
|
return self.value.prettify()
|
||||||
|
|
||||||
|
@ -65,6 +76,9 @@ class EmptyNode(Node):
|
||||||
class NodeCollection(Node):
|
class NodeCollection(Node):
|
||||||
nodes: Iterable[Tag]
|
nodes: Iterable[Tag]
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self.nodes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
return self.nodes
|
return self.nodes
|
||||||
|
@ -100,7 +114,7 @@ class SingleNode(Node):
|
||||||
return SingleNode(copy(self.value))
|
return SingleNode(copy(self.value))
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Template(Clonable):
|
class Template(Node):
|
||||||
_value: Node
|
_value: Node
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
|
from multiprocessing import Pool
|
||||||
from dataclasses import InitVar, dataclass, field
|
from dataclasses import InitVar, dataclass, field
|
||||||
from importlib import resources
|
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 bs4 import Tag
|
||||||
|
|
||||||
from puppygirl.elements import IdAttr, TemplateName
|
from puppygirl.elements import IdAttr, TemplateName
|
||||||
from puppygirl.elements.shadow_root import Node, SingleNode
|
from puppygirl.elements.shadow_root import Node, SingleNode, Template
|
||||||
from puppygirl.puppytype import Templates
|
from puppygirl.puppytype import RenderableElement, Templates
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
@ -23,13 +24,17 @@ class Query:
|
||||||
def query(self, tree: "BeautifulSoup") -> Iterable[Tag]:
|
def query(self, tree: "BeautifulSoup") -> Iterable[Tag]:
|
||||||
return tree.find_all(*self._args, **self._kwargs)
|
return tree.find_all(*self._args, **self._kwargs)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RenderState:
|
||||||
|
templates: "Templates"
|
||||||
|
|
||||||
@runtime_checkable
|
@runtime_checkable
|
||||||
class Renderable(Protocol):
|
class Renderable(Protocol):
|
||||||
def query(self) -> Query:
|
def query(self) -> Query:
|
||||||
return Query()
|
return Query()
|
||||||
|
|
||||||
def render(self, tree: "BeautifulSoup") -> "BeautifulSoup":
|
def render(self, node: "Node", state: RenderState) -> "RenderableElement":
|
||||||
return tree
|
return node
|
||||||
|
|
||||||
class Renderer(Protocol):
|
class Renderer(Protocol):
|
||||||
def render(self, puppygirl: "Puppygirl", tree: "BeautifulSoup") -> "BeautifulSoup":
|
def render(self, puppygirl: "Puppygirl", tree: "BeautifulSoup") -> "BeautifulSoup":
|
||||||
|
@ -60,24 +65,30 @@ class ServerSideRenderer(Renderer):
|
||||||
|
|
||||||
def render(self, puppygirl: "Puppygirl", tree: "BeautifulSoup") -> "BeautifulSoup":
|
def render(self, puppygirl: "Puppygirl", tree: "BeautifulSoup") -> "BeautifulSoup":
|
||||||
templates = ServerSideRenderer._find_local_templates(puppygirl, tree) | puppygirl.templates
|
templates = ServerSideRenderer._find_local_templates(puppygirl, tree) | puppygirl.templates
|
||||||
|
state = RenderState(templates)
|
||||||
|
|
||||||
for element in puppygirl.elements:
|
for element in puppygirl.elements:
|
||||||
if hasattr(element, "name"):
|
if hasattr(element, "name"):
|
||||||
query = element.query()
|
query = element.query()
|
||||||
tags = query.query(tree)
|
tags = query.query(tree)
|
||||||
tags.reverse()
|
tags.reverse()
|
||||||
|
|
||||||
for tag in tags:
|
for tag in tags:
|
||||||
|
node = SingleNode(tag)
|
||||||
new_tag = element.render(
|
new_tag = element.render(
|
||||||
SingleNode(tag),
|
node,
|
||||||
# SingleNode(tag).clone(),
|
state
|
||||||
templates
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(new_tag, Iterable):
|
if new_tag is None or new_tag.value == tag:
|
||||||
tag.extend(new_tag)
|
continue
|
||||||
tag.unwrap()
|
|
||||||
else:
|
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
|
return tree
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue