tests working, nice error messages
This commit is contained in:
parent
b5622ecc54
commit
291c308cad
5 changed files with 107 additions and 28 deletions
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
"""
|
||||
|
|
|
|||
77
src/ytdl_sub/script/utils/parser_exception_formatter.py
Normal file
77
src/ytdl_sub/script/utils/parser_exception_formatter.py
Normal 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)}")
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue