Improve error messages
This commit is contained in:
parent
b656fe55c2
commit
769c82e33b
@ -37,7 +37,10 @@ def guess_memon_version(obj: Any) -> Format:
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return Format.MEMON_LEGACY
|
return Format.MEMON_LEGACY
|
||||||
except TypeError:
|
except TypeError:
|
||||||
raise ValueError("Invalid JSON structure for memon file")
|
raise ValueError(
|
||||||
|
"This JSON file is not a correct memon file : the top-level "
|
||||||
|
"value is not an object"
|
||||||
|
)
|
||||||
|
|
||||||
if version == "0.1.0":
|
if version == "0.1.0":
|
||||||
return Format.MEMON_0_1_0
|
return Format.MEMON_0_1_0
|
||||||
|
@ -99,7 +99,9 @@ def parse_command(line: str) -> Tuple[str, Optional[str]]:
|
|||||||
return CommandVisitor().visit(command_grammar.parse(line)) # type: ignore
|
return CommandVisitor().visit(command_grammar.parse(line)) # type: ignore
|
||||||
except ParseError:
|
except ParseError:
|
||||||
if line.strip()[0] == "#":
|
if line.strip()[0] == "#":
|
||||||
raise ParseError(f"Invalid command syntax : {line}") from None
|
raise ParseError(
|
||||||
|
"Line starts with '#' but it couldn't be parsed as a valid command"
|
||||||
|
) from None
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
@ -93,6 +93,32 @@ class DoubleColumnChartLine:
|
|||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"{self.position} |{self.timing}|"
|
return f"{self.position} |{self.timing}|"
|
||||||
|
|
||||||
|
def raise_if_unfit(self, bytes_per_panel: int) -> None:
|
||||||
|
self.raise_if_position_unfit(bytes_per_panel)
|
||||||
|
self.raise_if_timing_unfit(bytes_per_panel)
|
||||||
|
|
||||||
|
def raise_if_position_unfit(self, bytes_per_panel: int) -> None:
|
||||||
|
expected_length = 4 * bytes_per_panel
|
||||||
|
actual_length = len(self.position.encode("shift-jis-2004"))
|
||||||
|
if expected_length != actual_length:
|
||||||
|
raise SyntaxError(
|
||||||
|
f"Invalid position part. Since #bpp={bytes_per_panel}, the \
|
||||||
|
position part of a line should be {expected_length} bytes long, \
|
||||||
|
but {self.position!r} is {actual_length} bytes long"
|
||||||
|
)
|
||||||
|
|
||||||
|
def raise_if_timing_unfit(self, bytes_per_panel: int) -> None:
|
||||||
|
if self.timing is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
length = len(self.timing.encode("shift-jis-2004"))
|
||||||
|
if length % bytes_per_panel != 0:
|
||||||
|
raise SyntaxError(
|
||||||
|
f"Invalid timing part. Since #bpp={bytes_per_panel}, the timing \
|
||||||
|
part of a line should be divisible by {bytes_per_panel}, but \
|
||||||
|
{self.timing!r} is {length} bytes long so it's not"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DoubleColumnChartLineVisitor(NodeVisitor):
|
class DoubleColumnChartLineVisitor(NodeVisitor):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@ -145,7 +171,10 @@ def split_double_byte_line(line: str) -> List[str]:
|
|||||||
"""
|
"""
|
||||||
encoded_line = line.encode("shift-jis-2004")
|
encoded_line = line.encode("shift-jis-2004")
|
||||||
if len(encoded_line) % 2 != 0:
|
if len(encoded_line) % 2 != 0:
|
||||||
raise ValueError(f"Invalid chart line : {line}")
|
raise ValueError(
|
||||||
|
"Line of odd length encountered while trying to split a double-byte "
|
||||||
|
f"line : {line!r}"
|
||||||
|
)
|
||||||
symbols = []
|
symbols = []
|
||||||
for i in range(0, len(encoded_line), 2):
|
for i in range(0, len(encoded_line), 2):
|
||||||
symbols.append(encoded_line[i : i + 2].decode("shift-jis-2004"))
|
symbols.append(encoded_line[i : i + 2].decode("shift-jis-2004"))
|
||||||
@ -166,7 +195,8 @@ class UnfinishedLongNote:
|
|||||||
def ends_at(self, end: BeatsTime) -> LongNote:
|
def ends_at(self, end: BeatsTime) -> LongNote:
|
||||||
if end < self.time:
|
if end < self.time:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Invalid end time ({end}) for long note starting at {self.time}"
|
"Invalid end time. A long note starting at "
|
||||||
|
f"{self.time} cannot end at {end} (which is earlier)"
|
||||||
)
|
)
|
||||||
return LongNote(
|
return LongNote(
|
||||||
time=self.time,
|
time=self.time,
|
||||||
@ -228,7 +258,7 @@ def pick_correct_long_note_candidates(
|
|||||||
solutions: List[Solution] = problem.getSolutions()
|
solutions: List[Solution] = problem.getSolutions()
|
||||||
if not solutions:
|
if not solutions:
|
||||||
raise SyntaxError(
|
raise SyntaxError(
|
||||||
"Invalid long note arrow pattern in bloc :\n"
|
"Impossible arrow pattern found in block :\n"
|
||||||
+ "\n".join("".join(line) for line in bloc)
|
+ "\n".join("".join(line) for line in bloc)
|
||||||
)
|
)
|
||||||
solution = min(solutions, key=long_note_solution_heuristic)
|
solution = min(solutions, key=long_note_solution_heuristic)
|
||||||
@ -283,7 +313,7 @@ class JubeatAnalyserParser:
|
|||||||
try:
|
try:
|
||||||
method = getattr(self, f"do_{command}")
|
method = getattr(self, f"do_{command}")
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise SyntaxError(f"Unknown analyser command : {command}") from None
|
raise SyntaxError(f"Unknown jubeat analyser command : {command}") from None
|
||||||
|
|
||||||
if value is not None:
|
if value is not None:
|
||||||
method(value)
|
method(value)
|
||||||
@ -341,7 +371,7 @@ class JubeatAnalyserParser:
|
|||||||
if bpp not in (1, 2):
|
if bpp not in (1, 2):
|
||||||
raise ValueError(f"Unexcpected bpp value : {value}")
|
raise ValueError(f"Unexcpected bpp value : {value}")
|
||||||
elif self.circle_free and bpp == 1:
|
elif self.circle_free and bpp == 1:
|
||||||
raise ValueError("#bpp can only be 2 when #circlefree is activated")
|
raise ValueError("Can't set #bpp to 1 when #circlefree is on")
|
||||||
else:
|
else:
|
||||||
self.bytes_per_panel = int(value)
|
self.bytes_per_panel = int(value)
|
||||||
|
|
||||||
@ -349,18 +379,17 @@ class JubeatAnalyserParser:
|
|||||||
self.hold_by_arrow = int(value) == 1
|
self.hold_by_arrow = int(value) == 1
|
||||||
|
|
||||||
def do_holdbytilde(self, value: str) -> None:
|
def do_holdbytilde(self, value: str) -> None:
|
||||||
if int(value):
|
raise NotImplementedError("jubeatools does not support #holdbytilde")
|
||||||
raise ValueError("jubeatools does not support #holdbytilde")
|
|
||||||
|
|
||||||
def do_circlefree(self, raw_value: str) -> None:
|
def do_circlefree(self, raw_value: str) -> None:
|
||||||
activate = bool(int(raw_value))
|
activate = bool(int(raw_value))
|
||||||
if activate and self.bytes_per_panel != 2:
|
if activate and self.bytes_per_panel != 2:
|
||||||
raise ValueError("#circlefree can only be activated when #bpp=2")
|
raise ValueError("#circlefree can only be on when #bpp=2")
|
||||||
self.circle_free = activate
|
self.circle_free = activate
|
||||||
|
|
||||||
def _wrong_format(self, f: str) -> None:
|
def _wrong_format(self, f: str) -> None:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"{f} command indicates this file uses another jubeat analyser "
|
f"{f} command means that this file uses another jubeat analyser "
|
||||||
"format than the one the currently selected parser is designed for"
|
"format than the one the currently selected parser is designed for"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -381,15 +410,15 @@ class JubeatAnalyserParser:
|
|||||||
length_as_shift_jis = len(symbol.encode("shift-jis-2004"))
|
length_as_shift_jis = len(symbol.encode("shift-jis-2004"))
|
||||||
if length_as_shift_jis != bpp:
|
if length_as_shift_jis != bpp:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Invalid symbol definition. Since #bpp={bpp}, timing symbols "
|
f"Invalid symbol definition. Since #bpp={bpp}, timing symbols \
|
||||||
f"should be {bpp} bytes long but '{symbol}' is {length_as_shift_jis}"
|
should be {bpp} bytes long but '{symbol}' is {length_as_shift_jis}"
|
||||||
)
|
)
|
||||||
if timing > self.beats_per_section:
|
if timing > self.beats_per_section:
|
||||||
message = (
|
raise ValueError(
|
||||||
"Invalid symbol definition conscidering the number of beats per section :\n"
|
f"Invalid symbol definition. Since sections only last \
|
||||||
f"*{symbol}:{timing}"
|
{self.beats_per_section} beats, a symbol cannot happen \
|
||||||
|
afterwards at {timing}"
|
||||||
)
|
)
|
||||||
raise ValueError(message)
|
|
||||||
self.symbols[symbol] = timing
|
self.symbols[symbol] = timing
|
||||||
|
|
||||||
def is_short_line(self, line: str) -> bool:
|
def is_short_line(self, line: str) -> bool:
|
||||||
|
@ -55,19 +55,28 @@ class Frame:
|
|||||||
bars: Dict[int, Dict[int, str]] = field(default_factory=dict)
|
bars: Dict[int, Dict[int, str]] = field(default_factory=dict)
|
||||||
|
|
||||||
def dump(self, length: Decimal) -> Iterator[str]:
|
def dump(self, length: Decimal) -> Iterator[str]:
|
||||||
# Check that bars are contiguous
|
self.raise_if_unfit()
|
||||||
for a, b in windowed(sorted(self.bars), 2):
|
|
||||||
if b is not None and a is not None and b - a != 1:
|
|
||||||
raise ValueError("Frame has discontinuous bars")
|
|
||||||
# Check all bars are in the same 4-bar group
|
|
||||||
if self.bars.keys() != set(bar % 4 for bar in self.bars):
|
|
||||||
raise ValueError("Frame contains bars from different 4-bar groups")
|
|
||||||
|
|
||||||
for pos, bar in zip_longest(self.dump_positions(), self.dump_bars(length)):
|
for pos, bar in zip_longest(self.dump_positions(), self.dump_bars(length)):
|
||||||
if bar is None:
|
if bar is None:
|
||||||
bar = ""
|
bar = ""
|
||||||
yield f"{pos} {bar}"
|
yield f"{pos} {bar}"
|
||||||
|
|
||||||
|
def raise_if_unfit(self) -> None:
|
||||||
|
if not self.bars_are_contiguous():
|
||||||
|
raise ValueError("Frame has discontinuous bars")
|
||||||
|
if not self.all_bars_are_from_the_same_group():
|
||||||
|
raise ValueError("Frame contains bars from different 4-bar groups")
|
||||||
|
|
||||||
|
def bars_are_contiguous(self) -> bool:
|
||||||
|
return all(
|
||||||
|
b - a == 1
|
||||||
|
for a, b in windowed(sorted(self.bars), 2)
|
||||||
|
if b is not None and a is not None
|
||||||
|
)
|
||||||
|
|
||||||
|
def all_bars_are_from_the_same_group(self) -> bool:
|
||||||
|
return self.bars.keys() == set(bar % 4 for bar in self.bars)
|
||||||
|
|
||||||
def dump_positions(self) -> Iterator[str]:
|
def dump_positions(self) -> Iterator[str]:
|
||||||
for y in range(4):
|
for y in range(4):
|
||||||
yield "".join(
|
yield "".join(
|
||||||
@ -121,9 +130,7 @@ class MemoDumpedSection(JubeatAnalyserDumpedSection):
|
|||||||
chosen_symbols[time_in_section] = symbol
|
chosen_symbols[time_in_section] = symbol
|
||||||
bars[bar_index][time_index] = symbol
|
bars[bar_index][time_index] = symbol
|
||||||
elif time_in_section not in self.symbols:
|
elif time_in_section not in self.symbols:
|
||||||
raise ValueError(
|
raise ValueError(f"No symbol defined for time : {time_in_section}")
|
||||||
f"No symbol defined for time in section : {time_in_section}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create frame by bar
|
# Create frame by bar
|
||||||
section_symbols = ChainMap(chosen_symbols, self.symbols)
|
section_symbols = ChainMap(chosen_symbols, self.symbols)
|
||||||
|
@ -98,13 +98,7 @@ class MemoParser(JubeatAnalyserParser):
|
|||||||
self._do_bpp(value)
|
self._do_bpp(value)
|
||||||
|
|
||||||
def append_chart_line(self, line: DoubleColumnChartLine) -> None:
|
def append_chart_line(self, line: DoubleColumnChartLine) -> None:
|
||||||
if len(line.position.encode("shift-jis-2004")) != 4 * self.bytes_per_panel:
|
line.raise_if_unfit(self.bytes_per_panel)
|
||||||
raise SyntaxError(
|
|
||||||
f"Invalid chart line for #bpp={self.bytes_per_panel} : {line}"
|
|
||||||
)
|
|
||||||
if line.timing is not None and self.bytes_per_panel == 2:
|
|
||||||
if len(line.timing.encode("shift-jis-2004")) % 2 != 0:
|
|
||||||
raise SyntaxError(f"Invalid chart line for #bpp=2 : {line}")
|
|
||||||
self.current_chart_lines.append(line)
|
self.current_chart_lines.append(line)
|
||||||
if len(self.current_chart_lines) == 4:
|
if len(self.current_chart_lines) == 4:
|
||||||
self._push_frame()
|
self._push_frame()
|
||||||
|
@ -92,13 +92,7 @@ class Memo1Parser(JubeatAnalyserParser):
|
|||||||
self._do_bpp(value)
|
self._do_bpp(value)
|
||||||
|
|
||||||
def append_chart_line(self, line: DoubleColumnChartLine) -> None:
|
def append_chart_line(self, line: DoubleColumnChartLine) -> None:
|
||||||
if len(line.position.encode("shift-jis-2004")) != 4 * self.bytes_per_panel:
|
line.raise_if_unfit(self.bytes_per_panel)
|
||||||
raise SyntaxError(
|
|
||||||
f"Invalid chart line for #bpp={self.bytes_per_panel} : {line}"
|
|
||||||
)
|
|
||||||
if line.timing is not None and self.bytes_per_panel == 2:
|
|
||||||
if len(line.timing.encode("shift-jis-2004")) % 2 != 0:
|
|
||||||
raise SyntaxError(f"Invalid chart line for #bpp=2 : {line}")
|
|
||||||
self.current_chart_lines.append(line)
|
self.current_chart_lines.append(line)
|
||||||
if len(self.current_chart_lines) == 4:
|
if len(self.current_chart_lines) == 4:
|
||||||
self._push_frame()
|
self._push_frame()
|
||||||
|
Loading…
Reference in New Issue
Block a user