Skip to content

Commit 04f598e

Browse files
committed
Tracking if, for, while and autocomplete individually
1 parent 95f75b3 commit 04f598e

File tree

1 file changed

+58
-10
lines changed

1 file changed

+58
-10
lines changed

adafruit_templateengine.py

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -387,8 +387,10 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
387387
indent, indentation_level = " ", 1
388388

389389
# Keep track of the template state
390-
forloop_iterables: "list[str]" = []
391-
autoescape_modes: "list[bool]" = ["default_on"]
390+
nested_if_statements: "list[str]" = []
391+
nested_for_loops: "list[str]" = []
392+
nested_while_loops: "list[str]" = []
393+
nested_autoescape_modes: "list[str]" = []
392394
last_token_was_block = False
393395

394396
# Resolve tokens
@@ -414,7 +416,10 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
414416
if token.startswith(r"{{ "):
415417
last_token_was_block = False
416418

417-
autoescape = autoescape_modes[-1] in ("on", "default_on")
419+
if nested_autoescape_modes:
420+
autoescape = nested_autoescape_modes[-1][14:-3] == "on"
421+
else:
422+
autoescape = True
418423

419424
# Expression should be escaped with language-specific function
420425
if autoescape:
@@ -436,6 +441,8 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
436441
if token.startswith(r"{% if "):
437442
function_string += indent * indentation_level + f"{token[3:-3]}:\n"
438443
indentation_level += 1
444+
445+
nested_if_statements.append(token)
439446
elif token.startswith(r"{% elif "):
440447
indentation_level -= 1
441448
function_string += indent * indentation_level + f"{token[3:-3]}:\n"
@@ -447,30 +454,51 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
447454
elif token == r"{% endif %}":
448455
indentation_level -= 1
449456

457+
if not nested_if_statements:
458+
raise SyntaxError("No matching {% if ... %} block for {% endif %}")
459+
460+
nested_if_statements.pop()
461+
450462
# Token is a for loop
451463
elif token.startswith(r"{% for "):
452464
function_string += indent * indentation_level + f"{token[3:-3]}:\n"
453465
indentation_level += 1
454466

455-
forloop_iterables.append(token[3:-3].split(" in ", 1)[1])
467+
nested_for_loops.append(token)
456468
elif token == r"{% empty %}":
457469
indentation_level -= 1
470+
last_forloop_iterable = nested_for_loops[-1][3:-3].split(" in ", 1)[1]
458471

459472
function_string += (
460-
indent * indentation_level + f"if not {forloop_iterables[-1]}:\n"
473+
indent * indentation_level + f"if not {last_forloop_iterable}:\n"
461474
)
462475
indentation_level += 1
463476
elif token == r"{% endfor %}":
464477
indentation_level -= 1
465-
forloop_iterables.pop()
478+
479+
if not nested_for_loops:
480+
raise SyntaxError(
481+
"No matching {% for ... %} block for {% endfor %}"
482+
)
483+
484+
nested_for_loops.pop()
466485

467486
# Token is a while loop
468487
elif token.startswith(r"{% while "):
469488
function_string += indent * indentation_level + f"{token[3:-3]}:\n"
470489
indentation_level += 1
490+
491+
nested_while_loops.append(token)
471492
elif token == r"{% endwhile %}":
472493
indentation_level -= 1
473494

495+
if not nested_while_loops:
496+
raise SyntaxError(
497+
"No matching {% while ... %} block for {% endwhile %}"
498+
)
499+
500+
nested_while_loops.pop()
501+
474502
# Token is a Python code
475503
elif token.startswith(r"{% exec "):
476504
expression = token[8:-3]
@@ -481,11 +509,16 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
481509
mode = token[14:-3]
482510
if mode not in ("on", "off"):
483511
raise ValueError(f"Unknown autoescape mode: {mode}")
484-
autoescape_modes.append(mode)
512+
513+
nested_autoescape_modes.append(token)
514+
485515
elif token == r"{% endautoescape %}":
486-
if autoescape_modes == ["default_on"]:
487-
raise ValueError("No autoescape mode to end")
488-
autoescape_modes.pop()
516+
if not nested_autoescape_modes:
517+
raise SyntaxError(
518+
"No matching {% autoescape ... %} block for {% endautoescape %}"
519+
)
520+
521+
nested_autoescape_modes.pop()
489522

490523
else:
491524
raise ValueError(
@@ -498,6 +531,21 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
498531
# Continue with the rest of the template
499532
template = template[token_match.end() :]
500533

534+
# Checking for unclosed blocks
535+
if len(nested_if_statements) > 0:
536+
last_if_statement = nested_if_statements[-1]
537+
raise SyntaxError(f"Missing {{% endif %}} for {last_if_statement}")
538+
539+
if len(nested_for_loops) > 0:
540+
last_for_loop = nested_for_loops[-1]
541+
raise SyntaxError(f"Missing {{% endfor %}} for {last_for_loop}")
542+
543+
if len(nested_while_loops) > 0:
544+
last_while_loop = nested_while_loops[-1]
545+
raise SyntaxError(f"Missing {{% endwhile %}} for {last_while_loop}")
546+
547+
# No check for unclosed autoescape blocks, as they are optional and do not result in errors
548+
501549
# Add the text after the last token (if any)
502550
text_after_last_token = template
503551

0 commit comments

Comments
 (0)