refactor: merge Base, Implicit and Named FillNodes into FillNode

This commit is contained in:
Juro Oravec 2024-04-15 10:56:28 +02:00
parent c0c9e145a9
commit 9d9462162a
3 changed files with 40 additions and 46 deletions

View file

@ -23,11 +23,8 @@ from django_components.component_registry import AlreadyRegistered, ComponentReg
from django_components.logger import logger
from django_components.middleware import is_dependency_middleware_active
from django_components.slots import (
DEFAULT_SLOT_KEY,
OUTER_CONTEXT_CONTEXT_KEY,
FillNode,
FillContent,
ImplicitFillNode,
NamedFillNode,
SlotName,
render_component_template_with_slots,
)
@ -299,7 +296,7 @@ class ComponentNode(Node):
context_args: List[FilterExpression],
context_kwargs: Mapping[str, FilterExpression],
isolated_context: bool = False,
fill_nodes: Sequence[Union[ImplicitFillNode, NamedFillNode]] = (),
fill_nodes: Sequence[FillNode] = (),
) -> None:
self.name_fexp = name_fexp
self.context_args = context_args or []
@ -330,7 +327,7 @@ class ComponentNode(Node):
resolved_context_args = [safe_resolve(arg, context) for arg in self.context_args]
resolved_context_kwargs = {key: safe_resolve(kwarg, context) for key, kwarg in self.context_kwargs.items()}
if len(self.fill_nodes) == 1 and isinstance(self.fill_nodes[0], ImplicitFillNode):
if len(self.fill_nodes) == 1 and self.fill_nodes[0].is_implicit:
fill_content: Dict[str, FillContent] = {DEFAULT_SLOT_KEY: FillContent(self.fill_nodes[0].nodelist, None)}
else:
fill_content = {}

View file

@ -1,4 +1,5 @@
import difflib
import json
import sys
from typing import Dict, List, NamedTuple, Optional, Sequence, Set, Tuple, Type, Union
@ -13,7 +14,7 @@ else:
from typing import TypeAlias
from django.template import Context, Template
from django.template.base import FilterExpression, Node, NodeList, TextNode
from django.template.base import FilterExpression, Node, NodeList, TextNode, Parser
from django.template.defaulttags import CommentNode
from django.template.exceptions import TemplateSyntaxError
from django.utils.safestring import SafeString, mark_safe
@ -149,12 +150,25 @@ class SlotNode(Node, TemplateAwareNodeMixin):
raise ValueError(f"Unknown value for SLOT_CONTEXT_BEHAVIOR: '{app_settings.SLOT_CONTEXT_BEHAVIOR}'")
class BaseFillNode(Node):
def __init__(self, nodelist: NodeList):
self.nodelist: NodeList = nodelist
class FillNode(Node):
is_implicit: bool
"""
Set when a `component` tag pair is passed template content that
excludes `fill` tags. Nodes of this type contribute their nodelists to slots marked
as 'default'.
"""
def __repr__(self) -> str:
raise NotImplementedError
def __init__(
self,
nodelist: NodeList,
name_fexp: FilterExpression,
alias_fexp: Optional[FilterExpression] = None,
is_implicit: bool = False,
):
self.nodelist: NodeList = nodelist
self.name_fexp = name_fexp
self.alias_fexp = alias_fexp
self.is_implicit = is_implicit
def render(self, context: Context) -> str:
raise TemplateSyntaxError(
@ -162,19 +176,7 @@ class BaseFillNode(Node):
"You are probably seeing this because you have used one outside "
"a {% component %} context."
)
class NamedFillNode(BaseFillNode):
def __init__(
self,
nodelist: NodeList,
name_fexp: FilterExpression,
alias_fexp: Optional[FilterExpression] = None,
):
super().__init__(nodelist)
self.name_fexp = name_fexp
self.alias_fexp = alias_fexp
def __repr__(self) -> str:
return f"<{type(self)} Name: {self.name_fexp}. Contents: {repr(self.nodelist)}.>"
@ -192,17 +194,6 @@ class NamedFillNode(BaseFillNode):
return resolved_alias
class ImplicitFillNode(BaseFillNode):
"""
Instantiated when a `component` tag pair is passed template content that
excludes `fill` tags. Nodes of this type contribute their nodelists to slots marked
as 'default'.
"""
def __repr__(self) -> str:
return f"<{type(self)} Contents: {repr(self.nodelist)}.>"
class _IfSlotFilledBranchNode(Node):
def __init__(self, nodelist: NodeList) -> None:
self.nodelist = nodelist
@ -272,7 +263,7 @@ class IfSlotFilledNode(Node):
def parse_slot_fill_nodes_from_component_nodelist(
component_nodelist: NodeList,
ComponentNodeCls: Type[Node],
) -> Sequence[Union[NamedFillNode, ImplicitFillNode]]:
) -> Sequence[FillNode]:
"""
Given a component body (`django.template.NodeList`), find all slot fills,
whether defined explicitly with `{% fill %}` or implicitly.
@ -291,7 +282,7 @@ def parse_slot_fill_nodes_from_component_nodelist(
Then this function returns the nodes (`django.template.Node`) for `fill "first_fill"`
and `fill "second_fill"`.
"""
fill_nodes: Sequence[Union[NamedFillNode, ImplicitFillNode]] = []
fill_nodes: Sequence[FillNode] = []
if _block_has_content(component_nodelist):
for parse_fn in (
_try_parse_as_default_fill,
@ -314,11 +305,11 @@ def parse_slot_fill_nodes_from_component_nodelist(
def _try_parse_as_named_fill_tag_set(
nodelist: NodeList,
ComponentNodeCls: Type[Node],
) -> Sequence[NamedFillNode]:
) -> Sequence[FillNode]:
result = []
seen_name_fexps: Set[FilterExpression] = set()
for node in nodelist:
if isinstance(node, NamedFillNode):
if isinstance(node, FillNode):
if node.name_fexp in seen_name_fexps:
raise TemplateSyntaxError(
f"Multiple fill tags cannot target the same slot name: "
@ -338,11 +329,11 @@ def _try_parse_as_named_fill_tag_set(
def _try_parse_as_default_fill(
nodelist: NodeList,
ComponentNodeCls: Type[Node],
) -> Sequence[ImplicitFillNode]:
) -> Sequence[FillNode]:
nodes_stack: List[Node] = list(nodelist)
while nodes_stack:
node = nodes_stack.pop()
if isinstance(node, NamedFillNode):
if isinstance(node, FillNode):
return []
elif isinstance(node, ComponentNodeCls):
# Stop searching here, as fill tags are permitted inside component blocks
@ -351,7 +342,13 @@ def _try_parse_as_default_fill(
for nodelist_attr_name in node.child_nodelists:
nodes_stack.extend(getattr(node, nodelist_attr_name, []))
else:
return [ImplicitFillNode(nodelist=nodelist)]
return [
FillNode(
nodelist=nodelist,
name_fexp=FilterExpression(json.dumps(DEFAULT_SLOT_KEY), Parser('')),
is_implicit=True,
)
]
def _block_has_content(nodelist: NodeList) -> bool:

View file

@ -19,7 +19,7 @@ from django_components.slots import (
IfSlotFilledConditionBranchNode,
IfSlotFilledElseBranchNode,
IfSlotFilledNode,
NamedFillNode,
FillNode,
SlotNode,
_IfSlotFilledBranchNode,
parse_slot_fill_nodes_from_component_nodelist,
@ -156,7 +156,7 @@ def do_slot(parser: Parser, token: Token) -> SlotNode:
@register.tag("fill")
def do_fill(parser: Parser, token: Token) -> NamedFillNode:
def do_fill(parser: Parser, token: Token) -> FillNode:
"""Block tag whose contents 'fill' (are inserted into) an identically named
'slot'-block in the component template referred to by a parent component.
It exists to make component nesting easier.
@ -182,7 +182,7 @@ def do_fill(parser: Parser, token: Token) -> NamedFillNode:
nodelist = parser.parse(parse_until=["endfill"])
parser.delete_first_token()
return NamedFillNode(
return FillNode(
nodelist,
name_fexp=FilterExpression(tgt_slot_name, tag),
alias_fexp=alias_fexp,