diff --git a/CHANGELOG.md b/CHANGELOG.md index 38406da..787177b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Changed - Minimum required Python version is now 3.9 ## Fixed +- Most loaders would incorrectly use the internal enum value name as difficulty + names for charts (like `Difficulty.EXTREME`) instead of the regular "display" + name (like `EXT`), not anymore ! - [eve] + [jbsq] - Custom hakus were not taken into account when computing the time of the last event, not anymore ! diff --git a/jubeatools/formats/jubeat_analyser/load_tools.py b/jubeatools/formats/jubeat_analyser/load_tools.py index 24ab9bb..cae6e52 100644 --- a/jubeatools/formats/jubeat_analyser/load_tools.py +++ b/jubeatools/formats/jubeat_analyser/load_tools.py @@ -27,9 +27,9 @@ from .symbols import ( ) DIFFICULTIES = { - 1: Difficulty.BASIC, - 2: Difficulty.ADVANCED, - 3: Difficulty.EXTREME, + 1: Difficulty.BASIC.value, + 2: Difficulty.ADVANCED.value, + 3: Difficulty.EXTREME.value, } SYMBOL_TO_BEATS_TIME = {c: BeatsTime("1/4") * i for i, c in enumerate(NOTE_SYMBOLS)} diff --git a/jubeatools/formats/konami/eve/load.py b/jubeatools/formats/konami/eve/load.py index fab9e37..c759544 100644 --- a/jubeatools/formats/konami/eve/load.py +++ b/jubeatools/formats/konami/eve/load.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import Any, Iterator, List, Optional +from typing import Any, Iterator, List from jubeatools import song from jubeatools.formats.load_tools import make_folder_loader @@ -23,8 +23,8 @@ load_folder = make_folder_loader("*.eve", load_file) def _load_eve(lines: List[str], file_path: Path, *, beat_snap: int = 240) -> song.Song: chart = make_chart_from_events(iter_events(lines), beat_snap=beat_snap) - dif = guess_difficulty(file_path.stem) or song.Difficulty.EXTREME - return song.Song(metadata=song.Metadata(), charts={dif: chart}) + dif = guess_difficulty(file_path.stem) + return song.Song(metadata=song.Metadata(), charts={dif.value: chart}) def iter_events(lines: List[str]) -> Iterator[Event]: @@ -72,8 +72,8 @@ def parse_event(line: str) -> Event: return Event(tick, command, value) -def guess_difficulty(filename: str) -> Optional[song.Difficulty]: +def guess_difficulty(filename: str) -> song.Difficulty: try: return song.Difficulty(filename.upper()) except ValueError: - return None + return song.Difficulty.EXTREME diff --git a/jubeatools/formats/konami/eve/tests/test_eve.py b/jubeatools/formats/konami/eve/tests/test_eve.py index 0026e4c..3d6c788 100644 --- a/jubeatools/formats/konami/eve/tests/test_eve.py +++ b/jubeatools/formats/konami/eve/tests/test_eve.py @@ -1,9 +1,11 @@ +from enum import Enum + from hypothesis import given from jubeatools import song from jubeatools.formats import Format from jubeatools.formats.konami.testutils import eve_compatible_song -from jubeatools.testutils.test_patterns import dump_and_load_then_compare +from jubeatools.testutils.test_patterns import dump_and_load, dump_and_load_then_compare @given(eve_compatible_song()) @@ -14,3 +16,19 @@ def test_that_full_chart_roundtrips(song: song.Song) -> None: bytes_decoder=lambda b: b.decode("ascii"), load_options={"beat_snap": 12}, ) + + +@given(eve_compatible_song()) +def test_that_difficulty_name_is_loaded_properly(original_song: song.Song) -> None: + recovered_song = dump_and_load( + Format.EVE, + original_song, + bytes_decoder=lambda b: b.decode("ascii"), + load_options={"beat_snap": 12}, + ) + original_dif, _ = original_song.charts.popitem() + recovered_dif, _ = recovered_song.charts.popitem() + assert type(original_dif) == str + assert not isinstance(original_dif, Enum) + assert type(recovered_dif) == str + assert not isinstance(recovered_dif, Enum) diff --git a/jubeatools/formats/konami/jbsq/load.py b/jubeatools/formats/konami/jbsq/load.py index b607188..2e6b785 100644 --- a/jubeatools/formats/konami/jbsq/load.py +++ b/jubeatools/formats/konami/jbsq/load.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import Any, Optional +from typing import Any from jubeatools import song from jubeatools.formats.load_tools import make_folder_loader @@ -31,8 +31,8 @@ def load_jbsq_file( raw_data = construct.jbsq.parse(bytes_) events = [make_event_from_construct(e) for e in raw_data.events] chart = make_chart_from_events(events, beat_snap=beat_snap) - dif = guess_difficulty(file_path.stem) or song.Difficulty.EXTREME - return song.Song(metadata=song.Metadata(), charts={dif: chart}) + dif = guess_difficulty(file_path.stem) + return song.Song(metadata=song.Metadata(), charts={dif.value: chart}) def make_event_from_construct(e: construct.Event) -> konami.Event: @@ -43,8 +43,8 @@ def make_event_from_construct(e: construct.Event) -> konami.Event: ) -def guess_difficulty(filename: str) -> Optional[song.Difficulty]: +def guess_difficulty(filename: str) -> song.Difficulty: try: return song.Difficulty(filename[-3:].upper()) except ValueError: - return None + return song.Difficulty.EXTREME diff --git a/jubeatools/formats/konami/jbsq/test_jbsq.py b/jubeatools/formats/konami/jbsq/test_jbsq.py index 15bf74e..497aa47 100644 --- a/jubeatools/formats/konami/jbsq/test_jbsq.py +++ b/jubeatools/formats/konami/jbsq/test_jbsq.py @@ -1,9 +1,11 @@ +from enum import Enum + from hypothesis import given from jubeatools import song from jubeatools.formats import Format from jubeatools.formats.konami.testutils import eve_compatible_song -from jubeatools.testutils.test_patterns import dump_and_load_then_compare +from jubeatools.testutils.test_patterns import dump_and_load, dump_and_load_then_compare from .construct import jbsq @@ -16,3 +18,19 @@ def test_that_full_chart_roundtrips(song: song.Song) -> None: bytes_decoder=lambda b: str(jbsq.parse(b)), load_options={"beat_snap": 12}, ) + + +@given(eve_compatible_song()) +def test_that_difficulty_name_is_loaded_properly(original_song: song.Song) -> None: + recovered_song = dump_and_load( + Format.JBSQ, + original_song, + bytes_decoder=lambda b: str(jbsq.parse(b)), + load_options={"beat_snap": 12}, + ) + original_dif, _ = original_song.charts.popitem() + recovered_dif, _ = recovered_song.charts.popitem() + assert type(original_dif) == str + assert not isinstance(original_dif, Enum) + assert type(recovered_dif) == str + assert not isinstance(recovered_dif, Enum) diff --git a/jubeatools/formats/konami/testutils.py b/jubeatools/formats/konami/testutils.py index e4fa4d9..0138be6 100644 --- a/jubeatools/formats/konami/testutils.py +++ b/jubeatools/formats/konami/testutils.py @@ -14,7 +14,7 @@ simple_beat_strat = jbst.beat_time( def eve_compatible_song(draw: st.DrawFn) -> song.Song: """eve only keeps notes, hakus, timing info and difficulty, the precision you can get out of it is also severly limited""" - diff = draw(st.sampled_from(list(song.Difficulty))) + diff = draw(st.sampled_from(list(d.value for d in song.Difficulty))) chart = draw( jbst.chart( timing_strat=jbst.timing_info( diff --git a/jubeatools/formats/malody/load.py b/jubeatools/formats/malody/load.py index 05a5dc9..23a8cdb 100644 --- a/jubeatools/formats/malody/load.py +++ b/jubeatools/formats/malody/load.py @@ -39,7 +39,7 @@ def load_malody_file(raw_dict: dict) -> song.Song: time_map = load_timing_info(file.time, bgm) timing = time_map.convert_to_timing_info() chart = song.Chart(level=Decimal(0), timing=timing, notes=load_notes(file.note)) - dif = file.meta.version or song.Difficulty.EXTREME + dif = file.meta.version or song.Difficulty.EXTREME.value return song.Song(metadata=metadata, charts={dif: chart}) diff --git a/jubeatools/formats/malody/tests/test_malody.py b/jubeatools/formats/malody/tests/test_malody.py index 7bafd5e..d4d01ef 100644 --- a/jubeatools/formats/malody/tests/test_malody.py +++ b/jubeatools/formats/malody/tests/test_malody.py @@ -16,8 +16,8 @@ from jubeatools.testutils.test_patterns import dump_and_load_then_compare @st.composite def difficulty(draw: st.DrawFn) -> str: - d: song.Difficulty = draw(st.sampled_from(list(song.Difficulty))) - return d.value + diff: str = draw(st.sampled_from(list(d.value for d in song.Difficulty))) + return diff @st.composite diff --git a/jubeatools/song.py b/jubeatools/song.py index ce737ea..e1e2878 100644 --- a/jubeatools/song.py +++ b/jubeatools/song.py @@ -21,7 +21,6 @@ from typing import ( Iterable, Iterator, List, - Mapping, Optional, Sequence, Set, @@ -271,6 +270,9 @@ class Difficulty(str, Enum): ADVANCED = "ADV" EXTREME = "EXT" + def __str__(self) -> str: + return self.value + @dataclass class Song: @@ -278,7 +280,7 @@ class Song: A Song is a set of charts with associated metadata""" metadata: Metadata - charts: Mapping[str, Chart] = field(default_factory=dict) + charts: Dict[str, Chart] = field(default_factory=dict) common_timing: Optional[Timing] = None common_hakus: Optional[Set[BeatsTime]] = None diff --git a/jubeatools/testutils/test_patterns.py b/jubeatools/testutils/test_patterns.py index e2171ef..f48a4ac 100644 --- a/jubeatools/testutils/test_patterns.py +++ b/jubeatools/testutils/test_patterns.py @@ -24,6 +24,20 @@ def dump_and_load_then_compare( load_options: Optional[dict] = None, dump_options: Optional[dict] = None, ) -> None: + recovered_song = dump_and_load( + format_, song, bytes_decoder, temp_path, load_options, dump_options + ) + assert recovered_song == song + + +def dump_and_load( + format_: Format, + song: song.Song, + bytes_decoder: Callable[[bytes], str] = lambda b: b.decode("utf-8"), + temp_path: Callable[[], ContextManager[Path]] = open_temp_dir, + load_options: Optional[dict] = None, + dump_options: Optional[dict] = None, +) -> song.Song: load_options = load_options or {} dump_options = dump_options or {} loader = LOADERS[format_] @@ -39,4 +53,4 @@ def dump_and_load_then_compare( recovered_song = loader(folder_path, **load_options) recovered_song.minimize_timings() recovered_song.minimize_hakus() - assert recovered_song == song + return recovered_song