8000 Syntax error prevention and traceback readability by michalpokusa · Pull Request #3 · adafruit/Adafruit_CircuitPython_TemplateEngine · GitHub
[go: up one dir, main page]

Skip to content

Syntax error prevention and traceback readability #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 71 additions & 23 deletions adafruit_templateengine.py
DCE9
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ def _resolve_includes(template: str):

def _check_for_unsupported_nested_blocks(template: str):
if _find_block(template) is not None:
raise ValueError("Nested blocks are not supported")
raise SyntaxError("Nested blocks are not supported")


def _resolve_includes_blocks_and_extends(template: str):
Expand Down Expand Up @@ -248,7 +248,7 @@ def _resolve_includes_blocks_and_extends(template: str):
endblock_match = _find_named_endblock(template, block_name)

if endblock_match is None:
raise ValueError(r"Missing {% endblock %} for block: " + block_name)
raise SyntaxError("Missing {% endblock %} for block: " + block_name)

block_content = template[block_match.end() : endblock_match.start()]

Expand Down Expand Up @@ -366,13 +366,13 @@ def _token_is_on_own_line(text_before_token: str) -> bool:
return _LSTRIP_BLOCK_PATTERN.search(text_before_token) is not None


def _create_template_function( # pylint: disable=,too-many-locals,too-many-branches,too-many-statements
def _create_template_rendering_function( # pylint: disable=,too-many-locals,too-many-branches,too-many-statements
template: str,
language: str = Language.HTML,
*,
trim_blocks: bool = True,
lstrip_blocks: bool = True,
function_name: str = "_",
function_name: str = "__template_rendering_function",
context_name: str = "context",
dry_run: bool = False,
) -> "Generator[str] | str":
Expand All @@ -387,8 +387,10 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
indent, indentation_level = " ", 1

# Keep track of the template state
forloop_iterables: "list[str]" = []
autoescape_modes: "list[bool]" = ["default_on"]
nested_if_statements: "list[str]" = []
nested_for_loops: "list[str]" = []
nested_while_loops: "list[str]" = []
nested_autoescape_modes: "list[str]" = []
last_token_was_block = False

# Resolve tokens
Expand All @@ -405,16 +407,21 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
if last_token_was_block and text_before_token.startswith("\n"):
text_before_token = text_before_token[1:]

if text_before_token:
function_string += (
indent * indentation_level + f"yield {repr(text_before_token)}\n"
)
if text_before_token:
function_string += (
indent * indentation_level + f"yield {repr(text_before_token)}\n"
)
else:
function_string += indent * indentation_level + "pass\n"

# Token is an expression
if token.startswith(r"{{ "):
last_token_was_block = False

autoescape = autoescape_modes[-1] in ("on", "default_on")
if nested_autoescape_modes:
autoescape = nested_autoescape_modes[-1][14:-3] == "on"
else:
autoescape = True

# Expression should be escaped with language-specific function
if autoescape:
Expand All @@ -436,6 +443,8 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
if token.startswith(r"{% if "):
function_string += indent * indentation_level + f"{token[3:-3]}:\n"
indentation_level += 1

nested_if_statements.append(token)
elif token.startswith(r"{% elif "):
indentation_level -= 1
function_string += indent * indentation_level + f"{token[3:-3]}:\n"
Expand All @@ -447,30 +456,49 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
elif token == r"{% endif %}":
indentation_level -= 1

if not nested_if_statements:
raise SyntaxError("Missing {% if ... %} block for {% endif %}")

nested_if_statements.pop()

# Token is a for loop
elif token.startswith(r"{% for "):
function_string += indent * indentation_level + f"{token[3:-3]}:\n"
indentation_level += 1

forloop_iterables.append(token[3:-3].split(" in ", 1)[1])
nested_for_loops.append(token)
elif token == r"{% empty %}":
indentation_level -= 1
last_forloop_iterable = nested_for_loops[-1][3:-3].split(" in ", 1)[1]

function_string += (
indent * indentation_level + f"if not {forloop_iterables[-1]}:\n"
indent * indentation_level + f"if not {last_forloop_iterable}:\n"
)
indentation_level += 1
elif token == r"{% endfor %}":
indentation_level -= 1
forloop_iterables.pop()

if not nested_for_loops:
raise SyntaxError("Missing {% for ... %} block for {% endfor %}")

nested_for_loops.pop()

# Token is a while loop
elif token.startswith(r"{% while "):
function_string += indent * indentation_level + f"{token[3:-3]}:\n"
indentation_level += 1

nested_while_loops.append(token)
elif token == r"{% endwhile %}":
indentation_level -= 1

if not nested_while_loops:
raise SyntaxError(
"Missing {% while ... %} block for {% endwhile %}"
)

nested_while_loops.pop()

# Token is a Python code
elif token.startswith(r"{% exec "):
expression = token[8:-3]
Expand All @@ -481,23 +509,41 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
mode = token[14:-3]
if mode not in ("on", "off"):
raise ValueError(f"Unknown autoescape mode: {mode}")
autoescape_modes.append(mode)

nested_autoescape_modes.append(token)

elif token == r"{% endautoescape %}":
if autoescape_modes == ["default_on"]:
raise ValueError("No autoescape mode to end")
autoescape_modes.pop()
if not nested_autoescape_modes:
raise SyntaxError(
"Missing {% autoescape ... %} block for {% endautoescape %}"
)

nested_autoescape_modes.pop()

else:
raise ValueError(
f"Unknown token type: {token} at {token_match.start()}"
)
raise SyntaxError(f"Unknown token type: {token}")

else:
raise ValueError(f"Unknown token type: {token} at {token_match.start()}")
raise SyntaxError(f"Unknown token type: {token}")

# Continue with the rest of the template
template = template[token_match.end() :]

# Checking for unclosed blocks
if len(nested_if_statements) > 0:
last_if_statement = nested_if_statements[-1]
raise SyntaxError("Missing {% endif %} for " + last_if_statement)

if len(nested_for_loops) > 0:
last_for_loop = nested_for_loops[-1]
raise SyntaxError("Missing {% endfor %} for " + last_for_loop)

if len(nested_while_loops) > 0:
last_while_loop = nested_while_loops[-1]
raise SyntaxError("Missing {% endwhile %} for " + last_while_loop)

# No check for unclosed autoescape blocks, as they are optional and do not result in errors

# Add the text after the last token (if any)
text_after_last_token = template

Expand Down Expand Up @@ -557,7 +603,9 @@ def __init__(self, template_string: str, *, language: str = Language.HTML) -> No
:param str template_string: String containing the template to be rendered
:param str language: Language for autoescaping. Defaults to HTML
"""
self._template_function = _create_template_function(template_string, language)
self._template_function = _create_template_rendering_function(
template_string, language
)

def render_iter(
self, context: dict = None, *, chunk_size: int = None
Expand Down
0