1
0
mirror of synced 2025-01-31 20:15:26 +01:00

Switch to pathlib + mypy stuff

This commit is contained in:
Stepland 2021-04-29 00:27:58 +02:00
parent 25e13c0936
commit 979b0e648f
20 changed files with 149 additions and 123 deletions

1
.gitignore vendored
View File

@ -2,4 +2,5 @@
.vscode
.hypothesis
.pytest_cache
.mypy_cache
dist

View File

@ -2,7 +2,7 @@
from typing import Optional
import click
from path import Path
from pathlib import Path
from jubeatools.formats import DUMPERS, LOADERS
from jubeatools.formats.enum import JUBEAT_ANALYSER_FORMATS

View File

@ -4,7 +4,7 @@ Module containing all the load/dump code for all file formats
from typing import IO, Any, Callable, Dict
from path import Path
from pathlib import Path
from jubeatools.song import Song
@ -41,7 +41,7 @@ LOADERS: Dict[Format, Callable[[Path], Song]] = {
Format.MEMO_2: load_memo2,
}
DUMPERS: Dict[str, Dumper] = {
DUMPERS: Dict[Format, Dumper] = {
Format.MEMON_LEGACY: dump_memon_legacy,
Format.MEMON_0_1_0: dump_memon_0_1_0,
Format.MEMON_0_2_0: dump_memon_0_2_0,

View File

@ -2,13 +2,13 @@ import json
import re
from typing import Any, List
from path import Path
from pathlib import Path
from .enum import Format
def guess_format(path: Path) -> Format:
if path.isdir():
if path.is_dir():
raise ValueError("Can't guess chart format for a folder")
# The file is valid json => memon

View File

@ -5,10 +5,10 @@ from dataclasses import dataclass, field
from decimal import Decimal
from fractions import Fraction
from itertools import chain
from typing import Callable, Dict, Iterator, List, Mapping, Optional, Union
from typing import Callable, Dict, Iterator, List, Mapping, Optional, Union, TypeVar
from more_itertools import collapse, intersperse, mark_ends, windowed
from path import Path
from pathlib import Path
from sortedcontainers import SortedDict, SortedKeyList
from jubeatools.formats.filetypes import ChartFile
@ -111,15 +111,19 @@ class SortedDefaultDict(SortedDict):
return value
# Here we split dataclass and ABC stuff since mypy curently can't handle both
# at once on a single class definition
@dataclass
class JubeatAnalyserDumpedSection(ABC):
class _JubeatAnalyerDumpedSection:
current_beat: BeatsTime
length: Decimal = 4
length: Decimal = Decimal(4)
commands: Dict[str, Optional[str]] = field(default_factory=dict)
symbol_definitions: Dict[BeatsTime, str] = field(default_factory=dict)
symbols: Dict[BeatsTime, str] = field(default_factory=dict)
notes: List[Union[TapNote, LongNote, LongNoteEnd]] = field(default_factory=list)
class JubeatAnalyserDumpedSection(_JubeatAnalyerDumpedSection, ABC):
def _dump_commands(self) -> Iterator[str]:
keys = chain(COMMAND_ORDER, self.commands.keys() - set(COMMAND_ORDER))
for key in keys:
@ -139,14 +143,16 @@ class JubeatAnalyserDumpedSection(ABC):
...
S = TypeVar('S', bound=JubeatAnalyserDumpedSection)
def create_sections_from_chart(
section_factory: Callable[[BeatsTime], JubeatAnalyserDumpedSection],
section_factory: Callable[[BeatsTime], S],
chart: Chart,
difficulty: str,
timing: Timing,
metadata: Metadata,
circle_free: bool,
) -> Mapping[BeatsTime, JubeatAnalyserDumpedSection]:
) -> Mapping[BeatsTime, S]:
sections = SortedDefaultDict(section_factory)
timing_events = sorted(timing.events, key=lambda e: e.time)
@ -225,7 +231,7 @@ def jubeat_analyser_file_dumper(
) -> Dict[Path, bytes]:
files = internal_dumper(song, circle_free)
res = {}
if path.isdir():
if path.is_dir():
name_format = song.metadata.title + " {difficulty}{dedup_index}.txt"
else:
name_format = "{base}{dedup_index}{ext}"
@ -233,18 +239,18 @@ def jubeat_analyser_file_dumper(
for chartfile in files:
i = 0
filepath = name_format.format(
base=path.stripext(),
base=path.parent / path.stem,
difficulty = DIFFICULTIES.get(chartfile.difficulty, chartfile.difficulty),
dedup_index = "" if i == 0 else f"-{i}",
ext=path.ext,
ext=path.suffix,
)
while filepath in res:
i += 1
filepath = name_format.format(
base=path.stripext(),
base=path.parent / path.stem,
difficulty = DIFFICULTIES.get(chartfile.difficulty, chartfile.difficulty),
dedup_index = "" if i == 0 else f"-{i}",
ext=path.ext,
ext=path.suffix,
)
res[Path(filepath)] = chartfile.contents.getvalue().encode(

View File

@ -1,23 +1,23 @@
from typing import Dict, List
from path import Path
from pathlib import Path
def load_files(path: Path) -> Dict[Path, List[str]]:
# The vast majority of memo files you will encounter will be propely
# decoded using shift-jis-2004. Get ready for endless fun with the small
# portion of files that won't
files = {}
if path.isdir():
for f in path.files("*.txt"):
files: Dict[Path, List[str]] = {}
if path.is_dir():
for f in path.glob("*.txt"):
_load_file(f, files)
elif path.isfile():
elif path.is_file():
_load_file(path, files)
return files
def _load_file(path: Path, files: Dict[Path, List[str]]):
try:
files[path] = path.lines("shift-jis-2004")
files[path] = path.read_text(encoding="shift-jis-2004").split("\n")
except UnicodeDecodeError:
pass

View File

@ -6,7 +6,7 @@ from copy import deepcopy
from dataclasses import astuple, dataclass
from decimal import Decimal
from itertools import product, zip_longest
from typing import Dict, Iterator, List, Optional, Set, Tuple
from typing import Dict, Iterator, List, Optional, Set, Tuple, AbstractSet
import constraint
from parsimonious import Grammar, NodeVisitor, ParseError
@ -164,7 +164,7 @@ class UnfinishedLongNote:
def find_long_note_candidates(
bloc: List[List[str]], note_symbols: Set[str], should_skip: Set[NotePosition]
bloc: List[List[str]], note_symbols: AbstractSet[str], should_skip: AbstractSet[NotePosition]
) -> Dict[NotePosition, Set[NotePosition]]:
"Return a dict of arrow position to landing note candidates"
arrow_to_note_candidates: Dict[NotePosition, Set[NotePosition]] = {}
@ -178,7 +178,7 @@ def find_long_note_candidates(
# at this point we are sure we have a long arrow
# we need to check in its direction for note candidates
note_candidates: Set[Tuple[int, int]] = set()
note_candidates = set()
𝛿pos = LONG_DIRECTION[symbol]
candidate = NotePosition(x, y) + 𝛿pos
while 0 <= candidate.x < 4 and 0 <= candidate.y < 4:

View File

@ -7,10 +7,10 @@ from functools import partial
from io import StringIO
from itertools import chain, zip_longest
from math import ceil
from typing import Dict, Iterator, List, Optional, Set, Tuple, Union
from typing import Dict, Iterator, List, Optional, Set, Tuple, Union, cast, Callable
from more_itertools import chunked, collapse, intersperse, mark_ends, windowed
from path import Path
from pathlib import Path
from sortedcontainers import SortedKeyList
from jubeatools import __version__
@ -259,11 +259,13 @@ def _dump_memo_chart(
def _dump_memo_internal(song: Song, circle_free: bool) -> List[ChartFile]:
files: List[ChartFile] = []
for difficulty, chart in song.charts.items():
timing = chart.timing or song.global_timing
assert timing is not None
contents = _dump_memo_chart(
difficulty,
chart,
song.metadata,
chart.timing or song.global_timing,
timing,
circle_free,
)
files.append(ChartFile(contents, song, difficulty, chart))

View File

@ -10,7 +10,7 @@ from typing import Dict, Iterator, List, Mapping, Optional, Set, Tuple, Union
import constraint
from more_itertools import collapse, mark_ends
from parsimonious import Grammar, NodeVisitor, ParseError
from path import Path
from pathlib import Path
from jubeatools.song import (
Chart,
@ -21,6 +21,7 @@ from jubeatools.song import (
Song,
TapNote,
Timing,
Preview,
)
from ..command import is_command, parse_command
@ -118,7 +119,10 @@ class MemoParser(JubeatAnalyserParser):
return list(line)
def _frames_duration(self) -> Decimal:
return sum(frame.duration for frame in self.frames)
return sum(
(frame.duration for frame in self.frames),
start=Decimal(0)
)
def _push_frame(self):
position_part = [
@ -198,7 +202,10 @@ class MemoParser(JubeatAnalyserParser):
frame_starting_beat = Decimal(0)
for i, frame in enumerate(section.frames):
if frame.timing_part:
frame_starting_beat = sum(f.duration for f in section.frames[:i])
frame_starting_beat = sum(
(f.duration for f in section.frames[:i]),
start=Decimal(0)
)
local_symbols = {
symbol: Decimal("0.25") * i + frame_starting_beat
for i, symbol in enumerate(collapse(frame.timing_part))
@ -315,8 +322,10 @@ def _load_memo_file(lines: List[str]) -> Song:
cover=parser.jacket,
)
if parser.preview_start is not None:
metadata.preview_start = SecondsTime(parser.preview_start) / 1000
metadata.preview_length = SecondsTime(10)
metadata.preview = Preview(
start=SecondsTime(parser.preview_start) / 1000,
length=SecondsTime(10)
)
timing = Timing(
events=parser.timing_events, beat_zero_offset=SecondsTime(parser.offset) / 1000

View File

@ -10,7 +10,7 @@ from math import ceil
from typing import Dict, Iterator, List, Optional, Set, Tuple, Union
from more_itertools import chunked, collapse, intersperse, mark_ends, windowed
from path import Path
from pathlib import Path
from sortedcontainers import SortedKeyList
from jubeatools import __version__
@ -58,7 +58,8 @@ class Frame:
def dump(self, length: Decimal) -> Iterator[str]:
# Check that bars are contiguous
for a, b in windowed(sorted(self.bars), 2):
if b is not None and b - a != 1:
if a is not None and b is not None:
if 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):
@ -104,7 +105,7 @@ class Memo1DumpedSection(JubeatAnalyserDumpedSection):
notes_by_bar[bar_index].append(note)
# Pre-render timing bars
bars: Dict[int, List[str]] = defaultdict(dict)
bars: Dict[int, List[str]] = defaultdict(list)
chosen_symbols: Dict[BeatsTime, str] = {}
symbols_iterator = iter(NOTE_SYMBOLS)
for bar_index in range(ceil(self.length)):
@ -227,7 +228,7 @@ def _dump_memo1_chart(
)
# Jubeat Analyser format command
sections[0].commands["memo1"] = None
sections[BeatsTime(0)].commands["memo1"] = None
# Actual output to file
file = StringIO()
@ -242,11 +243,13 @@ def _dump_memo1_chart(
def _dump_memo1_internal(song: Song, circle_free: bool) -> List[ChartFile]:
files: List[ChartFile] = []
for difficulty, chart in song.charts.items():
timing = chart.timing or song.global_timing
assert timing is not None
contents = _dump_memo1_chart(
difficulty,
chart,
song.metadata,
chart.timing or song.global_timing,
timing,
circle_free,
)
files.append(ChartFile(contents, song, difficulty, chart))

View File

@ -10,7 +10,7 @@ from typing import Dict, Iterator, List, Mapping, Optional, Set, Tuple, Union
import constraint
from more_itertools import collapse, mark_ends
from parsimonious import Grammar, NodeVisitor, ParseError
from path import Path
from pathlib import Path
from jubeatools.song import (
BeatsTime,
@ -22,6 +22,7 @@ from jubeatools.song import (
Song,
TapNote,
Timing,
Preview
)
from ..command import is_command, parse_command
@ -112,7 +113,7 @@ class Memo1Parser(JubeatAnalyserParser):
return list(line)
def _frames_duration(self) -> Decimal:
return sum(frame.duration for frame in self.frames)
return sum((frame.duration for frame in self.frames), start=Decimal(0))
def _push_frame(self):
position_part = [
@ -184,13 +185,13 @@ class Memo1Parser(JubeatAnalyserParser):
]:
"""iterate over tuples of
currently_defined_symbols, frame, section_starting_beat, section"""
local_symbols: Dict[str, Decimal] = {}
local_symbols = {}
section_starting_beat = Decimal(0)
for section in self.sections:
frame_starting_beat = Decimal(0)
for i, frame in enumerate(section.frames):
if frame.timing_part:
frame_starting_beat = sum(f.duration for f in section.frames[:i])
frame_starting_beat = sum((f.duration for f in section.frames[:i]), start=Decimal(0))
local_symbols = {
symbol: BeatsTime(symbol_index, len(bar))
+ bar_index
@ -309,8 +310,10 @@ def _load_memo1_file(lines: List[str]) -> Song:
cover=parser.jacket,
)
if parser.preview_start is not None:
metadata.preview_start = SecondsTime(parser.preview_start) / 1000
metadata.preview_length = SecondsTime(10)
metadata.preview = Preview(
start=SecondsTime(parser.preview_start) / 1000,
length=SecondsTime(10)
)
timing = Timing(
events=parser.timing_events, beat_zero_offset=SecondsTime(parser.offset) / 1000

View File

@ -10,7 +10,7 @@ from math import ceil
from typing import Dict, Iterator, List, Optional, Set, Tuple, Union
from more_itertools import chunked, collapse, intersperse, mark_ends, windowed
from path import Path
from pathlib import Path
from sortedcontainers import SortedKeyList
from jubeatools import __version__
@ -38,7 +38,6 @@ from ..dump_tools import (
DIRECTION_TO_ARROW,
DIRECTION_TO_LINE,
NOTE_TO_CIRCLE_FREE_SYMBOL,
JubeatAnalyserDumpedSection,
LongNoteEnd,
SortedDefaultDict,
create_sections_from_chart,
@ -61,7 +60,8 @@ class Frame:
def dump(self) -> Iterator[str]:
# Check that bars are contiguous
for a, b in windowed(sorted(self.bars), 2):
if b is not None and b - a != 1:
if a is not None and b is not None:
if b - a != 1:
raise ValueError("Frame has discontinuous bars")
for pos, bar in zip_longest(self.dump_positions(), self.dump_bars()):
@ -91,6 +91,13 @@ class StopEvent:
duration: SecondsTime
@dataclass
class BarEvent:
note: Optional[str] = None
bpms: List[BPMEvent] = field(default_factory=list)
stops: List[StopEvent] = field(default_factory=list)
@dataclass
class Memo2Section:
"""A 4-beat-long group of notes"""
@ -115,7 +122,7 @@ class Memo2Section:
events_by_bar[bar_index].append(event)
# Pre-render timing bars
bars: Dict[int, List[str]] = defaultdict(dict)
bars: Dict[int, List[str]] = defaultdict(list)
chosen_symbols: Dict[BeatsTime, str] = {}
symbols_iterator = iter(NOTE_SYMBOLS)
for bar_index in range(4):
@ -127,9 +134,8 @@ class Memo2Section:
)
if bar_length < 3:
bar_length = 4
bar_dict: Dict[int, List[Union[str, BPMEvent, StopEvent]]] = defaultdict(
list
)
bar_dict: Dict[int, BarEvent] = defaultdict(BarEvent)
for note in notes:
time_in_section = note.time % BeatsTime(4)
time_in_bar = note.time % Fraction(1)
@ -139,34 +145,28 @@ class Memo2Section:
if time_index not in bar_dict:
symbol = next(symbols_iterator)
chosen_symbols[time_in_section] = symbol
bar_dict[time_index].append(symbol)
bar_dict[time_index].note = symbol
for event in events:
time_in_bar = event.time % Fraction(1)
time_index = time_in_bar.numerator * (
bar_length / time_in_bar.denominator
)
bar_dict[time_index].append(event)
if isinstance(event, StopEvent):
bar_dict[time_index].stops.append(event)
elif isinstance(event, BPMEvent):
bar_dict[time_index].bpms.append(event)
bar = []
for i in range(bar_length):
events_and_note = bar_dict.get(i, [])
stops = list(
filter(lambda e: isinstance(e, StopEvent), events_and_note)
)
bpms = list(filter(lambda e: isinstance(e, BPMEvent), events_and_note))
notes = list(filter(lambda e: isinstance(e, str), events_and_note))
assert len(notes) <= 1
for stop in stops:
bar_event = bar_dict.get(i, BarEvent())
for stop in bar_event.stops:
bar.append(f"[{int(stop.duration * 1000)}]")
for bpm in bpms:
for bpm in bar_event.bpms:
bar.append(f"({bpm.BPM})")
if notes:
note = notes[0]
else:
note = EMPTY_BEAT_SYMBOL
bar.append(note)
bar.append(bar_event.note or EMPTY_BEAT_SYMBOL)
bars[bar_index] = bar
@ -331,11 +331,13 @@ def _dump_memo2_chart(
def _dump_memo2_internal(song: Song, circle_free: bool = False) -> List[ChartFile]:
files: List[ChartFile] = []
for difficulty, chart in song.charts.items():
timing = chart.timing or song.global_timing
assert timing is not None
contents = _dump_memo2_chart(
difficulty,
chart,
song.metadata,
chart.timing or song.global_timing,
timing,
circle_free,
)
files.append(ChartFile(contents, song, difficulty, chart))

View File

@ -10,7 +10,7 @@ from typing import Dict, Iterator, List, Mapping, Optional, Set, Tuple, Union
import constraint
from more_itertools import collapse, mark_ends
from parsimonious import Grammar, NodeVisitor, ParseError
from path import Path
from pathlib import Path
from jubeatools.song import (
BeatsTime,
@ -23,6 +23,7 @@ from jubeatools.song import (
Song,
TapNote,
Timing,
Preview,
)
from ..command import is_command, parse_command
@ -50,7 +51,7 @@ from ..symbols import CIRCLE_FREE_SYMBOLS, NOTE_SYMBOLS
@dataclass
class Notes:
class NoteCluster:
string: str
def dump(self) -> str:
@ -73,7 +74,7 @@ class BPM:
return f"({self.value})"
Event = Union[Notes, Stop, BPM]
Event = Union[NoteCluster, Stop, BPM]
@dataclass
@ -91,7 +92,6 @@ class RawMemo2ChartLine:
@dataclass
class Memo2ChartLine:
"""timing part only contains notes"""
position: str
timing: Optional[List[str]]
@ -136,7 +136,7 @@ class Memo2ChartLineVisitor(NodeVisitor):
self.time_part.append(BPM(Decimal(value.text)))
def visit_notes(self, node, visited_children):
self.time_part.append(Notes(node.text))
self.time_part.append(NoteCluster(node.text))
def generic_visit(self, node, visited_children):
...
@ -229,11 +229,12 @@ class Memo2Parser(JubeatAnalyserParser):
raise SyntaxError(
f"Invalid chart line for #bpp={self.bytes_per_panel} : {raw_line}"
)
if raw_line.timing is not None and self.bytes_per_panel == 2:
if any(
len(event.string.encode("shift-jis-2004")) % 2 != 0
for event in raw_line.timing
if isinstance(event, Notes)
len(e.string.encode("shift-jis-2004")) % 2 != 0
for e in raw_line.timing
if isinstance(e, NoteCluster)
):
raise SyntaxError(f"Invalid chart line for #bpp=2 : {raw_line}")
@ -241,12 +242,12 @@ class Memo2Parser(JubeatAnalyserParser):
line = Memo2ChartLine(raw_line.position, None)
else:
# split notes
bar = []
for event in raw_line.timing:
if isinstance(event, Notes):
bar.extend(self._split_chart_line(event.string))
bar: List[Union[str, Stop, BPM]] = []
for raw_event in raw_line.timing:
if isinstance(raw_event, NoteCluster):
bar.extend(self._split_chart_line(raw_event.string))
else:
bar.append(event)
bar.append(raw_event)
# extract timing info
bar_length = sum(1 for e in bar if isinstance(e, str))
symbol_duration = BeatsTime(1, bar_length)
@ -286,8 +287,8 @@ class Memo2Parser(JubeatAnalyserParser):
else:
return list(line)
def _frames_duration(self) -> Decimal:
return sum(frame.duration for frame in self.frames)
def _frames_duration(self) -> BeatsTime:
return sum((frame.duration for frame in self.frames), start=BeatsTime(0))
def _push_frame(self):
position_part = [
@ -336,13 +337,13 @@ class Memo2Parser(JubeatAnalyserParser):
def _iter_frames(
self,
) -> Iterator[Tuple[Mapping[str, BeatsTime], Memo2Frame, BeatsTime]]:
) -> Iterator[Tuple[Mapping[str, BeatsTime], Memo2Frame]]:
"""iterate over tuples of (currently_defined_symbols, frame)"""
local_symbols: Dict[str, Decimal] = {}
local_symbols = {}
frame_starting_beat = BeatsTime(0)
for i, frame in enumerate(self.frames):
if frame.timing_part:
frame_starting_beat = sum(f.duration for f in self.frames[:i])
frame_starting_beat = sum((f.duration for f in self.frames[:i]), start=BeatsTime(0))
local_symbols = {
symbol: frame_starting_beat
+ bar_index
@ -445,8 +446,7 @@ def _load_memo2_file(lines: List[str]) -> Song:
cover=parser.jacket,
)
if parser.preview_start is not None:
metadata.preview_start = SecondsTime(parser.preview_start) / 1000
metadata.preview_length = SecondsTime(10)
metadata.preview = Preview(start=SecondsTime(parser.preview_start) / 1000,length=SecondsTime(10))
timing = Timing(
events=parser.timing_events, beat_zero_offset=SecondsTime(parser.offset or 0) / 1000

View File

@ -9,7 +9,7 @@ from itertools import chain
from typing import Dict, Iterator, List, Mapping, Optional, Tuple
from more_itertools import collapse, intersperse, mark_ends, windowed
from path import Path
from pathlib import Path
from sortedcontainers import SortedKeyList
from jubeatools import __version__
@ -90,7 +90,7 @@ class MonoColumnDumpedSection(JubeatAnalyserDumpedSection):
frame = {}
time_in_section = note.time - self.current_beat
if circle_free:
symbol = CIRCLE_FREE_SYMBOLS[time_in_section]
symbol = CIRCLE_FREE_SYMBOLS[int(time_in_section)]
else:
symbol = self.symbols[time_in_section]
frame[note.position] = symbol
@ -177,11 +177,13 @@ def _dump_mono_column_chart(
def _dump_mono_column_internal(song: Song, circle_free: bool) -> List[ChartFile]:
files: List[ChartFile] = []
for difficulty, chart in song.charts.items():
timing = chart.timing or song.global_timing
assert timing is not None
contents = _dump_mono_column_chart(
difficulty,
chart,
song.metadata,
chart.timing or song.global_timing,
timing,
circle_free,
)
files.append(ChartFile(contents, song, difficulty, chart))

View File

@ -7,11 +7,11 @@ from decimal import Decimal
from enum import Enum
from functools import reduce
from itertools import product
from typing import Dict, Iterator, List, Set, Tuple
from typing import Dict, Iterator, List, Set, Tuple, Union
import constraint
from parsimonious import Grammar, NodeVisitor, ParseError
from path import Path
from pathlib import Path
from jubeatools.song import (
BeatsTime,
@ -24,7 +24,7 @@ from jubeatools.song import (
Song,
TapNote,
Timing,
Union,
Preview,
)
from ..command import is_command, parse_command
@ -296,8 +296,10 @@ def _load_mono_column_file(lines: List[str]) -> Song:
cover=parser.jacket,
)
if parser.preview_start is not None:
metadata.preview_start = SecondsTime(parser.preview_start) / 1000
metadata.preview_length = SecondsTime(10)
metadata.preview = Preview(
start=SecondsTime(parser.preview_start) / 1000,
length=SecondsTime(10)
)
timing = Timing(
events=parser.timing_events, beat_zero_offset=SecondsTime(parser.offset) / 1000

View File

@ -10,7 +10,7 @@ https://github.com/Stepland/memon
from io import StringIO
from itertools import chain
from typing import IO, Any, Dict, Iterable, List, Tuple, Union
from typing import IO, Any, Dict, Iterable, List, Tuple, Union, Mapping
import simplejson as json
from marshmallow import (
@ -22,7 +22,7 @@ from marshmallow import (
validate,
validates_schema,
)
from path import Path
from pathlib import Path
from jubeatools.song import *
from jubeatools.utils import lcm
@ -160,7 +160,7 @@ def load_memon_legacy(file: Path) -> Song:
events=[BPMEvent(time=BeatsTime(0), BPM=memon["metadata"]["BPM"])],
beat_zero_offset=SecondsTime(-memon["metadata"]["offset"]),
)
charts = MultiDict()
charts: MultiDict[Chart] = MultiDict()
for memon_chart in memon["data"]:
charts.add(
memon_chart["dif_name"],
@ -187,7 +187,7 @@ def load_memon_0_1_0(file: Path) -> Song:
events=[BPMEvent(time=BeatsTime(0), BPM=memon["metadata"]["BPM"])],
beat_zero_offset=SecondsTime(-memon["metadata"]["offset"]),
)
charts = MultiDict()
charts: MultiDict[Chart] = MultiDict()
for difficulty, memon_chart in memon["data"].items():
charts.add(
difficulty,
@ -221,7 +221,7 @@ def load_memon_0_2_0(file: Path) -> Song:
events=[BPMEvent(time=BeatsTime(0), BPM=memon["metadata"]["BPM"])],
beat_zero_offset=SecondsTime(-memon["metadata"]["offset"]),
)
charts = MultiDict()
charts: MultiDict[Chart] = MultiDict()
for difficulty, memon_chart in memon["data"].items():
charts.add(
difficulty,
@ -254,7 +254,7 @@ def _get_timing(song: Song) -> Timing:
return next(
chart.timing
for chart in song.charts.values()
if chart is not None
if chart.timing is not None
)
def _raise_if_unfit_for_v0(song: Song, version: str) -> None:
@ -337,7 +337,7 @@ def dump_memon_legacy(song: Song, path: Path) -> Dict[Path, bytes]:
_raise_if_unfit_for_v0(song, "legacy")
timing = _get_timing(song)
memon = {
memon: Dict[str, Any] = {
"metadata": {
"song title": song.metadata.title,
"artist": song.metadata.artist,
@ -364,7 +364,7 @@ def dump_memon_legacy(song: Song, path: Path) -> Dict[Path, bytes]:
}
)
if path.isdir():
if path.is_dir():
filepath = path / f"{song.metadata.title}.memon"
else:
filepath = path
@ -377,7 +377,7 @@ def dump_memon_0_1_0(song: Song, path: Path) -> Dict[Path, bytes]:
_raise_if_unfit_for_v0(song, "v0.1.0")
timing= _get_timing(song)
memon = {
memon: Dict[str, Any] = {
"version": "0.1.0",
"metadata": {
"song title": song.metadata.title,
@ -400,7 +400,7 @@ def dump_memon_0_1_0(song: Song, path: Path) -> Dict[Path, bytes]:
],
}
if path.isdir():
if path.is_dir():
filepath = path / f"{song.metadata.title}.memon"
else:
filepath = path
@ -413,7 +413,7 @@ def dump_memon_0_2_0(song: Song, path: Path) -> Dict[Path, bytes]:
_raise_if_unfit_for_v0(song, "v0.2.0")
timing = _get_timing(song)
memon = {
memon: Dict[str, Any] = {
"version": "0.2.0",
"metadata": {
"song title": song.metadata.title,
@ -443,7 +443,7 @@ def dump_memon_0_2_0(song: Song, path: Path) -> Dict[Path, bytes]:
],
}
if path.isdir():
if path.is_dir():
filepath = path / f"{song.metadata.title}.memon"
else:
filepath = path

View File

@ -2,7 +2,7 @@ import tempfile
import hypothesis.strategies as st
from hypothesis import given
from path import Path
from pathlib import Path
from jubeatools.testutils.strategies import NoteOption, TimingOption
from jubeatools.testutils.strategies import song as song_strat

View File

@ -1,12 +1,8 @@
from typing import Any, Dict, Protocol
from typing import Any, Dict, Callable
from path import Path
from pathlib import Path
from jubeatools.song import Song
class Dumper(Protocol):
def __call__(
self, song: Song, path: Path, **kwargs: Dict[str, Any]
) -> Dict[Path, bytes]:
...
Dumper = Callable[[Song, Path], Dict[Path, bytes]]

View File

@ -15,7 +15,7 @@ from functools import wraps
from typing import Iterator, List, Mapping, Optional, Type, Union
from multidict import MultiDict
from path import Path
from pathlib import Path
BeatsTime = Fraction
SecondsTime = Decimal
@ -164,7 +164,7 @@ class Song:
f"{self.metadata}\n"
f"{other.metadata}"
)
charts = MultiDict()
charts: MultiDict[Chart] = MultiDict()
charts.extend(self.charts)
charts.extend(other.charts)
if (

View File

@ -231,7 +231,7 @@ def song(
),
)
diffs = draw(st.sets(diff_name_strat, min_size=1, max_size=10))
charts = MultiDict()
charts: MultiDict[Chart] = MultiDict()
for diff_name in diffs:
chart_timing_strat = st.none()
if TimingOption.PER_CHART in timing_options: