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 FunctionArgument
|
||||||
from ytdl_sub.script.types.variable import Variable
|
from ytdl_sub.script.types.variable import Variable
|
||||||
from ytdl_sub.script.utils.exceptions import InvalidSyntaxException
|
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.utils.exceptions import StringFormattingException
|
||||||
from ytdl_sub.validators.string_formatter_validators import is_valid_source_variable_name
|
from ytdl_sub.validators.string_formatter_validators import is_valid_source_variable_name
|
||||||
|
|
||||||
|
|
@ -43,18 +45,10 @@ class _Parser:
|
||||||
parked_pos = self._pos
|
parked_pos = self._pos
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
except InvalidSyntaxException as exc:
|
except NonFormattedInvalidSyntaxException as exc:
|
||||||
border = 4
|
raise ParserExceptionFormatter(
|
||||||
text_left = max(0, parked_pos - border)
|
self._text, parked_pos, self._pos, exc
|
||||||
text_right = min(len(self._text), self._pos + border)
|
).highlight() from exc
|
||||||
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
|
|
||||||
|
|
||||||
def _read(self, increment_pos: bool = True, length: int = 1) -> Optional[str]:
|
def _read(self, increment_pos: bool = True, length: int = 1) -> Optional[str]:
|
||||||
try:
|
try:
|
||||||
|
|
@ -250,7 +244,7 @@ class _Parser:
|
||||||
while ch := self._read(increment_pos=False):
|
while ch := self._read(increment_pos=False):
|
||||||
if ch == "}":
|
if ch == "}":
|
||||||
if key is not None:
|
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
|
self._pos += 1
|
||||||
return UnresolvedMap(value=output)
|
return UnresolvedMap(value=output)
|
||||||
|
|
@ -264,16 +258,18 @@ class _Parser:
|
||||||
in_comma = True
|
in_comma = True
|
||||||
self._pos += 1
|
self._pos += 1
|
||||||
elif key is None:
|
elif key is None:
|
||||||
|
with self._error_formatting():
|
||||||
in_comma = False
|
in_comma = False
|
||||||
key_args = self._parse_args(breaking_chars=":")
|
key_args = self._parse_args(breaking_chars=":")
|
||||||
if len(key_args) != 1:
|
if len(key_args) != 1:
|
||||||
raise StringFormattingException("Lazy parsing but got mlutiple args")
|
raise StringFormattingException("Lazy parsing but got mlutiple args")
|
||||||
key = key_args[0]
|
key = key_args[0]
|
||||||
elif key is not None and ch == ":":
|
elif key is not None and ch == ":":
|
||||||
|
with self._error_formatting():
|
||||||
self._pos += 1
|
self._pos += 1
|
||||||
value_args = self._parse_args(breaking_chars=",}")
|
value_args = self._parse_args(breaking_chars=",}")
|
||||||
if len(value_args) != 1:
|
if len(value_args) != 1:
|
||||||
raise InvalidSyntaxException("Map has a key with no value")
|
raise NonFormattedInvalidSyntaxException("Map has a key with no value")
|
||||||
|
|
||||||
output[key] = value_args[0]
|
output[key] = value_args[0]
|
||||||
key = None
|
key = None
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@ class CustomFunction(Function):
|
||||||
resolved_variables=resolved_variables_with_args,
|
resolved_variables=resolved_variables_with_args,
|
||||||
custom_functions=custom_functions,
|
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")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,9 @@ from ytdl_sub.utils.exceptions import ValidationException
|
||||||
|
|
||||||
class InvalidSyntaxException(ValidationException):
|
class InvalidSyntaxException(ValidationException):
|
||||||
"""Syntax is incorrect"""
|
"""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):
|
def test_key_has_no_value(self, value: str):
|
||||||
# with pytest.raises(InvalidSyntaxException, match=re.escape("Map has a key with no value")):
|
with pytest.raises(InvalidSyntaxException, match=re.escape("Map has a key with no value")):
|
||||||
Script({"map": value}).resolve()
|
Script({"map": value}).resolve()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue