1212from .utils import logger
1313
1414
15- BLOCK_CALL = '{% call _get("[TAG]").render([ATTRS]) -%}[CONTENT]{%- endcall %}'
15+ BLOCK_CALL = '{% call(_slot="") _get("[TAG]").render([ATTRS]) -%}[CONTENT]{%- endcall %}'
1616INLINE_CALL = '{{ _get("[TAG]").render([ATTRS]) }}'
1717
1818re_raw = r"\{%-?\s*raw\s*-?%\}.+?\{%-?\s*endraw\s*-?%\}"
3333"""
3434RX_ATTR = re .compile (re_attr , re .VERBOSE | re .DOTALL )
3535
36+ RE_LSTRIP = r"\s*(?P<lstrip>-?)%}"
37+ RE_RSTRIP = r"{%(?P<rstrip>-?)\s*"
38+
39+ RE_SLOT_OPEN = r"{%-?\s*slot\s+(?P<name>[0-9A-Za-z_.:$-]+)" + RE_LSTRIP
40+ RE_SLOT_CLOSE = RE_RSTRIP + r"endslot\s*-?%}"
41+ RX_SLOT = re .compile (rf"{ RE_SLOT_OPEN } (?P<default>.*?)({ RE_SLOT_CLOSE } )" , re .DOTALL )
42+
43+ RE_FILL_OPEN = r"{%-?\s*fill\s+(?P<name>[0-9A-Za-z_.:$-]+)" + RE_LSTRIP
44+ RE_FILL_CLOSE = RE_RSTRIP + r"endfill\s*-?%}"
45+ RX_FILL = re .compile (rf"{ RE_FILL_OPEN } (?P<body>.*?)({ RE_FILL_CLOSE } )" , re .DOTALL )
46+
3647
3748def escape (s : t .Any , / ) -> Markup :
3849 return Markup (
@@ -72,7 +83,7 @@ def __init__(
7283 self .source = source
7384 self .components = components
7485
75- def parse (self , * , validate_tags : bool = True ) -> str :
86+ def parse (self , * , validate_tags : bool = True ) -> tuple [ str , tuple [ str , ...]] :
7687 """
7788 Parses the template source code.
7889
@@ -81,7 +92,8 @@ def parse(self, *, validate_tags: bool = True) -> str:
8192 Whether to raise an error for unknown TitleCased tags.
8293
8394 Returns:
84- The transformed template source code.
95+ - The transformed template source code
96+ - The list of slot names.
8597
8698 Raises:
8799 TemplateSyntaxError:
@@ -92,8 +104,9 @@ def parse(self, *, validate_tags: bool = True) -> str:
92104 source = self .source
93105 source , raw_blocks = self .replace_raw_blocks (source )
94106 source = self .process_tags (source , validate_tags = validate_tags )
107+ source , slots = self .process_slots (source )
95108 source = self .restore_raw_blocks (source , raw_blocks )
96- return source
109+ return source , slots
97110
98111 def replace_raw_blocks (self , source : str ) -> tuple [str , dict [str , str ]]:
99112 """
@@ -202,10 +215,93 @@ def replace_tag(
202215 content = source [end :index ]
203216 end = index + len (close_tag )
204217
218+ if content :
219+ content = self .process_fills (content )
220+
205221 attrs = self ._parse_attrs (raw_attrs )
206222 repl = self ._build_call (tag , attrs , content )
207223 return f"{ source [:start ]} { repl } { source [end :]} "
208224
225+ def process_slots (self , source : str ) -> tuple [str , tuple [str , ...]]:
226+ """
227+ Extracts slot content from the template source code.
228+
229+ Arguments:
230+ source:
231+ The template source code
232+
233+ Returns:
234+ - The transformed template source code
235+ - The list of slot names.
236+
237+ """
238+ slots = {}
239+ while True :
240+ match = RX_SLOT .search (source )
241+ if not match :
242+ break
243+ start , end = match .span (0 )
244+ slot_name = match .group ("name" )
245+ slot_default = match .group ("default" ) or ""
246+ lstrip = match .group ("lstrip" ) == "-"
247+ rstrip = match .group ("rstrip" ) == "-"
248+ if lstrip :
249+ slot_default = slot_default .lstrip ()
250+ if rstrip :
251+ slot_default = slot_default .rstrip ()
252+
253+ slot_expr = "" .join ([
254+ "{% if _slots.get('" , slot_name ,
255+ "') %}{{ _slots['" , slot_name ,
256+ "'] }}{% else %}" , slot_default ,
257+ "{% endif %}"
258+ ])
259+ source = f"{ source [:start ]} { slot_expr } { source [end :]} "
260+ slots [slot_name ] = 1
261+
262+ return source , tuple (slots .keys ())
263+
264+ def process_fills (self , source : str ) -> str :
265+ """
266+ Processes `{% fill slot_name %}...{% endfill %}` blocks in the template source code.
267+
268+ Arguments:
269+ source:
270+ The template source code.
271+
272+ Returns:
273+ The modified source code prepended by fill contents as `if` statements.
274+
275+ """
276+ fills = {}
277+
278+ while True :
279+ match = RX_FILL .search (source )
280+ if not match :
281+ break
282+ start , end = match .span (0 )
283+ fill_name = match .group ("name" )
284+ fill_body = match .group ("body" ) or ""
285+ lstrip = match .group ("lstrip" ) == "-"
286+ rstrip = match .group ("rstrip" ) == "-"
287+ if lstrip :
288+ fill_body = fill_body .lstrip ()
289+ if rstrip :
290+ fill_body = fill_body .rstrip ()
291+ fills [fill_name ] = fill_body
292+ source = f"{ source [:start ]} { source [end :]} "
293+
294+ if not fills :
295+ return source
296+
297+ ifs = []
298+ for fill_name , fill_body in fills .items ():
299+ ifs .append (f"{{% elif _slot == '{ fill_name } ' %}}{ fill_body } " )
300+ # Replace the first occurrence of "elif" with "if"
301+ str_ifs = f"\n {{% { '' .join (ifs )[5 :]} "
302+
303+ return f"{ str_ifs } {{% else -%}}\n { source .strip ()} \n {{%- endif %}}\n "
304+
209305 # Private
210306
211307 def _parse_opening_tag (
0 commit comments