tests working, nice error messages

This commit is contained in:
Jesse Bannon 2023-11-09 23:54:10 -08:00
parent b5622ecc54
commit 291c308cad
5 changed files with 107 additions and 28 deletions

View file

@ -15,6 +15,8 @@ from ytdl_sub.script.types.syntax_tree import SyntaxTree
from ytdl_sub.script.types.variable import FunctionArgument
from ytdl_sub.script.types.variable import Variable
from ytdl_sub.script.utils.exceptions import InvalidSyntaxException
from ytdl_sub.script.utils.exceptions import NonFormattedInvalidSyntaxException
from ytdl_sub.script.utils.parser_exception_formatter import ParserExceptionFormatter
from ytdl_sub.utils.exceptions import StringFormattingException
from ytdl_sub.validators.string_formatter_validators import is_valid_source_variable_name
@ -43,18 +45,10 @@ class _Parser:
parked_pos = self._pos
try:
yield
except InvalidSyntaxException as exc:
border = 4
text_left = max(0, parked_pos - border)
text_right = min(len(self._text), self._pos + border)
text_len = text_right - text_left
raise InvalidSyntaxException(
"Invalid syntax:\n"
f" {self._text[text_left:text_right]}\n"
f" {' ' * border}{'^' * text_len}\n\n"
f"{str(exc)}"
) from exc
except NonFormattedInvalidSyntaxException as exc:
raise ParserExceptionFormatter(
self._text, parked_pos, self._pos, exc
).highlight() from exc
def _read(self, increment_pos: bool = True, length: int = 1) -> Optional[str]:
try:
@ -250,7 +244,7 @@ class _Parser:
while ch := self._read(increment_pos=False):
if ch == "}":
if key is not None:
raise InvalidSyntaxException("Map has a key with no value")
raise NonFormattedInvalidSyntaxException("Map has a key with no value")
self._pos += 1
return UnresolvedMap(value=output)
@ -264,19 +258,21 @@ class _Parser:
in_comma = True
self._pos += 1
elif key is None:
in_comma = False
key_args = self._parse_args(breaking_chars=":")
if len(key_args) != 1:
raise StringFormattingException("Lazy parsing but got mlutiple args")
key = key_args[0]
with self._error_formatting():
in_comma = False
key_args = self._parse_args(breaking_chars=":")
if len(key_args) != 1:
raise StringFormattingException("Lazy parsing but got mlutiple args")
key = key_args[0]
elif key is not None and ch == ":":
self._pos += 1
value_args = self._parse_args(breaking_chars=",}")
if len(value_args) != 1:
raise InvalidSyntaxException("Map has a key with no value")
with self._error_formatting():
self._pos += 1
value_args = self._parse_args(breaking_chars=",}")
if len(value_args) != 1:
raise NonFormattedInvalidSyntaxException("Map has a key with no value")
output[key] = value_args[0]
key = None
output[key] = value_args[0]
key = None
else:
raise StringFormattingException("Invalid map")

View file

@ -197,8 +197,8 @@ class CustomFunction(Function):
resolved_variables=resolved_variables_with_args,
custom_functions=custom_functions,
)
else:
raise StringFormattingException(f"Custom function {self.name} does not exist")
raise StringFormattingException(f"Custom function {self.name} does not exist")
class BuiltInFunction(Function):

View file

@ -3,3 +3,9 @@ from ytdl_sub.utils.exceptions import ValidationException
class InvalidSyntaxException(ValidationException):
"""Syntax is incorrect"""
class NonFormattedInvalidSyntaxException(InvalidSyntaxException):
"""
Syntax is incorrect, and the exception itself has not been formatted yet to be user-facing
"""

View file

@ -0,0 +1,77 @@
import sys
from typing import List
from ytdl_sub.script.utils.exceptions import InvalidSyntaxException
from ytdl_sub.script.utils.exceptions import NonFormattedInvalidSyntaxException
class ParserExceptionFormatter:
def __init__(
self, text: str, start: int, end: int, exception: NonFormattedInvalidSyntaxException
):
self._text = text
self._start = start
self._end = end
self._exception = exception
def exception_text(self, border: int):
text_left = max(0, self._start - border)
text_right = min(len(self._text), self._start + border)
relative_start = self._start - text_left
exception_text: str = ""
if text_left > 3:
exception_text = ""
relative_start += len(exception_text)
exception_text += self._text[text_left:text_right]
if text_right < len(self._text) - 3:
exception_text += ""
exception_text += "\n"
exception_text += f"{' ' * relative_start}^"
return "\n" + exception_text
@property
def is_multi_line(self) -> bool:
return "\n" in self._text
def exception_text_lines(self, border_lines: int = 0) -> str:
split_text = self._text.split("\n")
start_line: int = sys.maxsize
end_line: int = -1
pos: int = 0
for idx, line in enumerate(split_text):
if self._start <= pos < self._end:
start_line = min(start_line, idx)
end_line = max(end_line, idx + 1)
pos += len(line)
true_start_line = start_line
start_line = max(0, start_line - border_lines)
end_line = min(len(split_text), end_line + border_lines)
# Get min leading spaces between all lines to return
min_leading_spaces = sys.maxsize
for line in split_text[start_line:end_line]:
min_leading_spaces = min(min_leading_spaces, len(line) - len(line.lstrip()))
to_return: List[str] = []
for idx in range(start_line, end_line):
if idx == true_start_line:
to_return.append(f">>> {split_text[idx][min_leading_spaces:]}")
else:
to_return.append(f" {split_text[idx][min_leading_spaces:]}")
return "\n" + "\n".join(to_return)
def highlight(self) -> InvalidSyntaxException:
if self.is_multi_line:
invalid_syntax = self.exception_text_lines(border_lines=3)
else:
invalid_syntax = self.exception_text(border=20)
return InvalidSyntaxException(f"{invalid_syntax}\n{str(self._exception)}")

View file

@ -72,5 +72,5 @@ class TestMap:
],
)
def test_key_has_no_value(self, value: str):
# with pytest.raises(InvalidSyntaxException, match=re.escape("Map has a key with no value")):
Script({"map": value}).resolve()
with pytest.raises(InvalidSyntaxException, match=re.escape("Map has a key with no value")):
Script({"map": value}).resolve()