1
0
mirror of synced 2024-11-14 18:38:03 +01:00

[jubeat-analyser] raise error when mono-column format is detected in other memo parsers

Update mypy
This commit is contained in:
Stepland 2021-05-02 18:14:51 +02:00
parent 769c82e33b
commit 22f8b3c33f
17 changed files with 1302 additions and 49 deletions

View File

@ -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

View File

@ -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:

View File

@ -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))

View File

@ -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(

View File

@ -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))

View File

@ -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)

View File

@ -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))

View File

@ -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)

View File

@ -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))

View File

@ -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

View File

@ -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"""

View 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)

View File

@ -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)

View File

@ -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
View File

@ -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"},

View File

@ -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"