[jubeat-analyser] raise error when mono-column format is detected in other memo parsers
Update mypy
This commit is contained in:
parent
769c82e33b
commit
22f8b3c33f
@ -1,3 +1,7 @@
|
||||
# Unreleased
|
||||
## Fixed
|
||||
- [jubeat-analyser] Raise exception earlier when a mono-column file is detected by the other #memo parsers (based on "--" separator lines)
|
||||
|
||||
# v0.1.3
|
||||
## Changed
|
||||
- [jubeat-analyser] Use "EXT" instead of "?" as the fallback difficulty name when loading
|
||||
|
@ -74,6 +74,13 @@ EMPTY_BEAT_SYMBOLS = {
|
||||
}
|
||||
|
||||
|
||||
SEPARATOR = re.compile(r"--.*")
|
||||
|
||||
|
||||
def is_separator(line: str) -> bool:
|
||||
return bool(SEPARATOR.match(line))
|
||||
|
||||
|
||||
double_column_chart_line_grammar = Grammar(
|
||||
r"""
|
||||
line = ws position_part ws (timing_part ws)? comment?
|
||||
@ -102,9 +109,9 @@ class DoubleColumnChartLine:
|
||||
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"
|
||||
f"Invalid position part. Since #bpp={bytes_per_panel}, the "
|
||||
f"position part of a line should be {expected_length} bytes long, "
|
||||
f"but {self.position!r} is {actual_length} bytes long"
|
||||
)
|
||||
|
||||
def raise_if_timing_unfit(self, bytes_per_panel: int) -> None:
|
||||
@ -114,9 +121,9 @@ class DoubleColumnChartLine:
|
||||
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"
|
||||
f"Invalid timing part. Since #bpp={bytes_per_panel}, the timing "
|
||||
f"part of a line should be divisible by {bytes_per_panel}, but "
|
||||
f"{self.timing!r} is {length} bytes long so it's not"
|
||||
)
|
||||
|
||||
|
||||
@ -245,7 +252,8 @@ Candidates = Dict[NotePosition, Set[NotePosition]]
|
||||
|
||||
|
||||
def pick_correct_long_note_candidates(
|
||||
arrow_to_note_candidates: Candidates, bloc: List[List[str]],
|
||||
arrow_to_note_candidates: Candidates,
|
||||
bloc: List[List[str]],
|
||||
) -> Solution:
|
||||
"""Believe it or not, assigning each arrow to a valid note candidate
|
||||
involves whipping out a CSP solver.
|
||||
@ -335,7 +343,10 @@ class JubeatAnalyserParser:
|
||||
def do_t(self, value: str) -> None:
|
||||
self.current_tempo = Decimal(value)
|
||||
self.timing_events.append(
|
||||
BPMEvent(BeatsTime(self.section_starting_beat), BPM=self.current_tempo,)
|
||||
BPMEvent(
|
||||
BeatsTime(self.section_starting_beat),
|
||||
BPM=self.current_tempo,
|
||||
)
|
||||
)
|
||||
|
||||
def do_pw(self, value: str) -> None:
|
||||
@ -410,14 +421,14 @@ class JubeatAnalyserParser:
|
||||
length_as_shift_jis = len(symbol.encode("shift-jis-2004"))
|
||||
if length_as_shift_jis != bpp:
|
||||
raise ValueError(
|
||||
f"Invalid symbol definition. Since #bpp={bpp}, timing symbols \
|
||||
should be {bpp} bytes long but '{symbol}' is {length_as_shift_jis}"
|
||||
f"Invalid symbol definition. Since #bpp={bpp}, timing symbols "
|
||||
f"should be {bpp} bytes long but '{symbol}' is {length_as_shift_jis}"
|
||||
)
|
||||
if timing > self.beats_per_section:
|
||||
raise ValueError(
|
||||
f"Invalid symbol definition. Since sections only last \
|
||||
{self.beats_per_section} beats, a symbol cannot happen \
|
||||
afterwards at {timing}"
|
||||
f"Invalid symbol definition. Since sections only last "
|
||||
f"{self.beats_per_section} beats, a symbol cannot happen "
|
||||
f"afterwards at {timing}"
|
||||
)
|
||||
self.symbols[symbol] = timing
|
||||
|
||||
@ -430,6 +441,14 @@ class JubeatAnalyserParser:
|
||||
else:
|
||||
return list(line)
|
||||
|
||||
def raise_if_separator(self, line: str, format_: str) -> None:
|
||||
if is_separator(line):
|
||||
raise SyntaxError(
|
||||
'Found a separator line (starting with "--") but the file '
|
||||
f"indicates it's using {format_} format, if the file is actually "
|
||||
f"in mono-column format (1列形式) there should be no {format_} line"
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DoubleColumnFrame:
|
||||
|
@ -269,7 +269,11 @@ def _dump_memo_internal(song: Song, circle_free: bool) -> List[ChartFile]:
|
||||
timing = chart.timing or song.global_timing
|
||||
assert timing is not None
|
||||
contents = _dump_memo_chart(
|
||||
difficulty, chart, song.metadata, timing, circle_free,
|
||||
difficulty,
|
||||
chart,
|
||||
song.metadata,
|
||||
timing,
|
||||
circle_free,
|
||||
)
|
||||
files.append(ChartFile(contents, song, difficulty, chart))
|
||||
|
||||
|
@ -81,6 +81,9 @@ class MemoLoadedSection:
|
||||
|
||||
|
||||
class MemoParser(JubeatAnalyserParser):
|
||||
|
||||
FORMAT_TAG = "#memo"
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.current_chart_lines: List[DoubleColumnChartLine] = []
|
||||
@ -153,6 +156,7 @@ class MemoParser(JubeatAnalyserParser):
|
||||
|
||||
def load_line(self, raw_line: str) -> None:
|
||||
line = raw_line.strip()
|
||||
self.raise_if_separator(line, self.FORMAT_TAG)
|
||||
if is_command(line):
|
||||
command, value = parse_command(line)
|
||||
self.handle_command(command, value)
|
||||
@ -241,7 +245,8 @@ class MemoParser(JubeatAnalyserParser):
|
||||
)
|
||||
if arrow_to_note_candidates:
|
||||
solution = pick_correct_long_note_candidates(
|
||||
arrow_to_note_candidates, frame.position_part,
|
||||
arrow_to_note_candidates,
|
||||
frame.position_part,
|
||||
)
|
||||
for arrow_pos, note_pos in solution.items():
|
||||
should_skip.add(arrow_pos)
|
||||
@ -291,9 +296,7 @@ def _load_memo_file(lines: List[str]) -> Song:
|
||||
try:
|
||||
parser.load_line(raw_line)
|
||||
except Exception as e:
|
||||
raise SyntaxError(
|
||||
f"Error while parsing memo line {i} :\n" f"{type(e).__name__}: {e}"
|
||||
) from None
|
||||
raise SyntaxError(f"On line {i}\n{e}") from None
|
||||
|
||||
parser.finish_last_few_notes()
|
||||
metadata = Metadata(
|
||||
|
@ -248,7 +248,11 @@ def _dump_memo1_internal(song: Song, circle_free: bool) -> List[ChartFile]:
|
||||
timing = chart.timing or song.global_timing
|
||||
assert timing is not None
|
||||
contents = _dump_memo1_chart(
|
||||
difficulty, chart, song.metadata, timing, circle_free,
|
||||
difficulty,
|
||||
chart,
|
||||
song.metadata,
|
||||
timing,
|
||||
circle_free,
|
||||
)
|
||||
files.append(ChartFile(contents, song, difficulty, chart))
|
||||
|
||||
|
@ -76,6 +76,9 @@ class Memo1LoadedSection:
|
||||
|
||||
|
||||
class Memo1Parser(JubeatAnalyserParser):
|
||||
|
||||
FORMAT_TAG = "#memo1"
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.current_chart_lines: List[DoubleColumnChartLine] = []
|
||||
@ -146,6 +149,7 @@ class Memo1Parser(JubeatAnalyserParser):
|
||||
|
||||
def load_line(self, raw_line: str) -> None:
|
||||
line = raw_line.strip()
|
||||
self.raise_if_separator(line, self.FORMAT_TAG)
|
||||
if is_command(line):
|
||||
command, value = parse_command(line)
|
||||
self.handle_command(command, value)
|
||||
@ -235,7 +239,8 @@ class Memo1Parser(JubeatAnalyserParser):
|
||||
)
|
||||
if arrow_to_note_candidates:
|
||||
solution = pick_correct_long_note_candidates(
|
||||
arrow_to_note_candidates, frame.position_part,
|
||||
arrow_to_note_candidates,
|
||||
frame.position_part,
|
||||
)
|
||||
for arrow_pos, note_pos in solution.items():
|
||||
should_skip.add(arrow_pos)
|
||||
|
@ -344,7 +344,11 @@ def _dump_memo2_internal(song: Song, circle_free: bool = False) -> List[ChartFil
|
||||
timing = chart.timing or song.global_timing
|
||||
assert timing is not None
|
||||
contents = _dump_memo2_chart(
|
||||
difficulty, chart, song.metadata, timing, circle_free,
|
||||
difficulty,
|
||||
chart,
|
||||
song.metadata,
|
||||
timing,
|
||||
circle_free,
|
||||
)
|
||||
files.append(ChartFile(contents, song, difficulty, chart))
|
||||
|
||||
|
@ -179,6 +179,9 @@ class Memo2Frame:
|
||||
|
||||
|
||||
class Memo2Parser(JubeatAnalyserParser):
|
||||
|
||||
FORMAT_TAG = "#memo2"
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.current_chart_lines: List[Memo2ChartLine] = []
|
||||
@ -302,6 +305,7 @@ class Memo2Parser(JubeatAnalyserParser):
|
||||
|
||||
def load_line(self, raw_line: str) -> None:
|
||||
line = raw_line.strip()
|
||||
self.raise_if_separator(line, self.FORMAT_TAG)
|
||||
if is_command(line):
|
||||
command, value = parse_command(line)
|
||||
self.handle_command(command, value)
|
||||
@ -319,7 +323,9 @@ class Memo2Parser(JubeatAnalyserParser):
|
||||
else:
|
||||
yield from self._iter_notes_without_longs()
|
||||
|
||||
def _iter_frames(self,) -> Iterator[Tuple[Mapping[str, BeatsTime], Memo2Frame]]:
|
||||
def _iter_frames(
|
||||
self,
|
||||
) -> Iterator[Tuple[Mapping[str, BeatsTime], Memo2Frame]]:
|
||||
"""iterate over tuples of (currently_defined_symbols, frame)"""
|
||||
local_symbols = {}
|
||||
frame_starting_beat = BeatsTime(0)
|
||||
@ -376,7 +382,8 @@ class Memo2Parser(JubeatAnalyserParser):
|
||||
)
|
||||
if arrow_to_note_candidates:
|
||||
solution = pick_correct_long_note_candidates(
|
||||
arrow_to_note_candidates, frame.position_part,
|
||||
arrow_to_note_candidates,
|
||||
frame.position_part,
|
||||
)
|
||||
for arrow_pos, note_pos in solution.items():
|
||||
should_skip.add(arrow_pos)
|
||||
|
@ -55,7 +55,10 @@ class MonoColumnDumpedSection(JubeatAnalyserDumpedSection):
|
||||
blocs.append(notes)
|
||||
return "\n".join(collapse([intersperse("", blocs), "--"]))
|
||||
|
||||
def _dump_notes(self, circle_free: bool = False,) -> Iterator[str]:
|
||||
def _dump_notes(
|
||||
self,
|
||||
circle_free: bool = False,
|
||||
) -> Iterator[str]:
|
||||
frames: List[Dict[NotePosition, str]] = []
|
||||
frame: Dict[NotePosition, str] = {}
|
||||
for note in self.notes:
|
||||
@ -180,7 +183,11 @@ def _dump_mono_column_internal(song: Song, circle_free: bool) -> List[ChartFile]
|
||||
timing = chart.timing or song.global_timing
|
||||
assert timing is not None
|
||||
contents = _dump_mono_column_chart(
|
||||
difficulty, chart, song.metadata, timing, circle_free,
|
||||
difficulty,
|
||||
chart,
|
||||
song.metadata,
|
||||
timing,
|
||||
circle_free,
|
||||
)
|
||||
files.append(ChartFile(contents, song, difficulty, chart))
|
||||
|
||||
|
@ -40,6 +40,7 @@ from ..load_tools import (
|
||||
decimal_to_beats,
|
||||
find_long_note_candidates,
|
||||
is_empty_line,
|
||||
is_separator,
|
||||
is_simple_solution,
|
||||
long_note_solution_heuristic,
|
||||
pick_correct_long_note_candidates,
|
||||
@ -80,13 +81,6 @@ def parse_mono_column_chart_line(line: str) -> str:
|
||||
return MonoColumnChartLineVisitor().visit(mono_column_chart_line_grammar.parse(line)) # type: ignore
|
||||
|
||||
|
||||
SEPARATOR = re.compile(r"--.*")
|
||||
|
||||
|
||||
def is_separator(line: str) -> bool:
|
||||
return bool(SEPARATOR.match(line))
|
||||
|
||||
|
||||
SYMBOL_TO_DECIMAL_TIME = {
|
||||
symbol: Decimal("0.25") * index for index, symbol in enumerate(NOTE_SYMBOLS)
|
||||
}
|
||||
@ -151,6 +145,10 @@ class MonoColumnParser(JubeatAnalyserParser):
|
||||
self.section_starting_beat += self.beats_per_section
|
||||
|
||||
def append_chart_line(self, line: str) -> None:
|
||||
expected_length = 4 * self.bytes_per_panel
|
||||
actual_length = len(line.encode("shift-jis-2004"))
|
||||
if actual_length != expected_length:
|
||||
raise SyntaxError(f"Invalid chart line. Since for ")
|
||||
if self.bytes_per_panel == 1 and len(line) != 4:
|
||||
raise SyntaxError(f"Invalid chart line for #bpp=1 : {line}")
|
||||
elif self.bytes_per_panel == 2 and len(line.encode("shift-jis-2004")) != 8:
|
||||
@ -229,7 +227,8 @@ class MonoColumnParser(JubeatAnalyserParser):
|
||||
)
|
||||
if arrow_to_note_candidates:
|
||||
solution = pick_correct_long_note_candidates(
|
||||
arrow_to_note_candidates, bloc,
|
||||
arrow_to_note_candidates,
|
||||
bloc,
|
||||
)
|
||||
for arrow_pos, note_pos in solution.items():
|
||||
should_skip.add(arrow_pos)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,2 @@
|
||||
"""This file is here so the test code can use importlib as a portable way to
|
||||
open test data in this folder"""
|
17
jubeatools/formats/jubeat_analyser/tests/test_examples.py
Normal file
17
jubeatools/formats/jubeat_analyser/tests/test_examples.py
Normal file
@ -0,0 +1,17 @@
|
||||
from functools import wraps
|
||||
from importlib import resources
|
||||
|
||||
import pytest
|
||||
|
||||
from jubeatools.formats import LOADERS
|
||||
from jubeatools.formats.guess import guess_format
|
||||
|
||||
from . import data
|
||||
|
||||
|
||||
def test_RorataJins_example() -> None:
|
||||
with pytest.raises(SyntaxError, match="separator line"):
|
||||
with resources.path(data, "RorataJin's example.txt") as p:
|
||||
format_ = guess_format(p)
|
||||
loader = LOADERS[format_]
|
||||
song = loader(p)
|
@ -265,7 +265,10 @@ def test_long_notes_ambiguous_case() -> None:
|
||||
"""
|
||||
expected = [
|
||||
LongNote(BeatsTime(0), NotePosition(x, y), BeatsTime(4), NotePosition(tx, ty))
|
||||
for (x, y), (tx, ty) in [((0, 0), (2, 0)), ((1, 0), (3, 0)),]
|
||||
for (x, y), (tx, ty) in [
|
||||
((0, 0), (2, 0)),
|
||||
((1, 0), (3, 0)),
|
||||
]
|
||||
]
|
||||
with pytest.warns(UserWarning):
|
||||
compare_chart_notes(chart, expected)
|
||||
@ -288,7 +291,10 @@ def test_long_notes_simple_solution_no_warning() -> None:
|
||||
"""
|
||||
expected = [
|
||||
LongNote(BeatsTime(0), NotePosition(x, y), BeatsTime(4), NotePosition(tx, ty))
|
||||
for (x, y), (tx, ty) in [((1, 1), (0, 1)), ((2, 1), (3, 1)),]
|
||||
for (x, y), (tx, ty) in [
|
||||
((1, 1), (0, 1)),
|
||||
((2, 1), (3, 1)),
|
||||
]
|
||||
]
|
||||
compare_chart_notes(chart, expected)
|
||||
|
||||
@ -309,7 +315,11 @@ def test_long_notes_complex_case() -> None:
|
||||
"""
|
||||
expected = [
|
||||
LongNote(BeatsTime(0), NotePosition(x, y), BeatsTime(4), NotePosition(tx, ty))
|
||||
for (x, y), (tx, ty) in [((1, 3), (1, 2)), ((2, 3), (2, 1)), ((3, 3), (0, 3)),]
|
||||
for (x, y), (tx, ty) in [
|
||||
((1, 3), (1, 2)),
|
||||
((2, 3), (2, 1)),
|
||||
((3, 3), (0, 3)),
|
||||
]
|
||||
]
|
||||
compare_chart_notes(chart, expected)
|
||||
|
||||
|
@ -160,7 +160,10 @@ def bpm_change(draw: DrawFunc) -> BPMEvent:
|
||||
|
||||
|
||||
@st.composite
|
||||
def timing_info(draw: DrawFunc, bpm_changes: bool = True,) -> Timing:
|
||||
def timing_info(
|
||||
draw: DrawFunc,
|
||||
bpm_changes: bool = True,
|
||||
) -> Timing:
|
||||
first_bpm = draw(bpm_strat())
|
||||
first_event = BPMEvent(BeatsTime(0), first_bpm)
|
||||
events = [first_event]
|
||||
|
23
poetry.lock
generated
23
poetry.lock
generated
@ -30,23 +30,24 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "19.10b0"
|
||||
version = "21.4b2"
|
||||
description = "The uncompromising code formatter."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.6.2"
|
||||
|
||||
[package.dependencies]
|
||||
appdirs = "*"
|
||||
attrs = ">=18.1.0"
|
||||
click = ">=6.5"
|
||||
pathspec = ">=0.6,<1"
|
||||
regex = "*"
|
||||
toml = ">=0.9.4"
|
||||
typed-ast = ">=1.4.0"
|
||||
click = ">=7.1.2"
|
||||
mypy-extensions = ">=0.4.3"
|
||||
pathspec = ">=0.8.1,<1"
|
||||
regex = ">=2020.1.8"
|
||||
toml = ">=0.10.1"
|
||||
|
||||
[package.extras]
|
||||
colorama = ["colorama (>=0.4.3)"]
|
||||
d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
|
||||
python2 = ["typed-ast (>=1.4.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
@ -340,7 +341,7 @@ python-versions = "*"
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "95f474b138c41e0c0ed74e4ccf573c90ea1f0b9281e91a1763fad2299a6a9f49"
|
||||
content-hash = "2f391539f8bf0d106956ec4f499fc1c98a2305e94cbd2682d913885d78d7a4dd"
|
||||
|
||||
[metadata.files]
|
||||
appdirs = [
|
||||
@ -356,8 +357,8 @@ attrs = [
|
||||
{file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"},
|
||||
]
|
||||
black = [
|
||||
{file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"},
|
||||
{file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"},
|
||||
{file = "black-21.4b2-py3-none-any.whl", hash = "sha256:bff7067d8bc25eb21dcfdbc8c72f2baafd9ec6de4663241a52fb904b304d391f"},
|
||||
{file = "black-21.4b2.tar.gz", hash = "sha256:fc9bcf3b482b05c1f35f6a882c079dc01b9c7795827532f4cc43c0ec88067bbc"},
|
||||
]
|
||||
click = [
|
||||
{file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
|
||||
|
@ -21,7 +21,7 @@ python-constraint = "^1.4.0"
|
||||
[tool.poetry.dev-dependencies]
|
||||
pytest = "^6.2.3"
|
||||
rope = "^0.17.0"
|
||||
black = "^19.10b0"
|
||||
black = "^21.4b2"
|
||||
hypothesis = "^6.10.1"
|
||||
mypy = "^0.812"
|
||||
isort = "^4.3.21"
|
||||
|
Loading…
Reference in New Issue
Block a user