diff --git a/jubeatools/formats/__init__.py b/jubeatools/formats/__init__.py index efc586d..32a11c0 100644 --- a/jubeatools/formats/__init__.py +++ b/jubeatools/formats/__init__.py @@ -8,7 +8,7 @@ from path import Path from jubeatools.song import Song -from .memo.mono_column import dump_mono_column, load_mono_column +from .jubeat_analyser.mono_column import dump_mono_column, load_mono_column from .memon import ( dump_memon_0_1_0, dump_memon_0_2_0, diff --git a/jubeatools/formats/jubeat_analyser/__init__.py b/jubeatools/formats/jubeat_analyser/__init__.py new file mode 100644 index 0000000..b0241c0 --- /dev/null +++ b/jubeatools/formats/jubeat_analyser/__init__.py @@ -0,0 +1,17 @@ +""" +Formats read by jubeat analyser + +memo is a vague term refering to several legacy formats. +They were originally derived from the (somewhat) human-readable format choosen +by websites storing official jubeat charts in text form as a memory aid. + +The machine-readable variants are partially documented (in japanese) +on these pages : +- http://yosh52.web.fc2.com/jubeat/fumenformat.html +- http://yosh52.web.fc2.com/jubeat/holdmarker.html +""" + +from .mono_column.dump import dump_mono_column +from .mono_column.load import load_mono_column +from .memo.dump import dump_memo +from .memo.load import load_memo diff --git a/jubeatools/formats/memo/command.py b/jubeatools/formats/jubeat_analyser/command.py similarity index 67% rename from jubeatools/formats/memo/command.py rename to jubeatools/formats/jubeat_analyser/command.py index b4958f6..03b2924 100644 --- a/jubeatools/formats/memo/command.py +++ b/jubeatools/formats/jubeat_analyser/command.py @@ -1,6 +1,29 @@ """ -Useful things to parse and dump the header of analyser-like formats +Useful things to parse and dump the jubeat analyser command format + +Known simple commands : + - b= : beats per measure (4 by default) + - m="" : music file path + - o= : offset in ms (100 by default) + - r= : ? increase the offset ? (in ms) (not supported, couldn't find any examples, wtf is it for ?) + - t= : tempo + +Known hash commands : + - #memo # youbeat-like format but a bar division always means a 1/4 note (amongst other quirks) + - #memo1 # youbeat-like but without bpm changes in the bar + - #memo2 # youbeat-like with memo symbols + - #boogie # youbeat-like + - #pw= # number of panels horizontally (4 by default) + - #ph= # number of panels vertically (4 by default) + - #lev= # chart level (typically 1 to 10) + - #dif={1, 2, 3} # 1: BSC, 2: ADV, 3: EXT + - #title="" # music title + - #artist="" # artist's name + - #jacket="" # music cover art path + - #prevpos= # preview start (in ms) + - #bpp # bytes per panel (2 by default) """ + from decimal import Decimal from numbers import Number from typing import Any, Iterable, List, Optional, Tuple, Union diff --git a/jubeatools/formats/jubeat_analyser/files.py b/jubeatools/formats/jubeat_analyser/files.py new file mode 100644 index 0000000..f827edc --- /dev/null +++ b/jubeatools/formats/jubeat_analyser/files.py @@ -0,0 +1,20 @@ +from path import Path +from typing import Dict, List + +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"): + _load_file(f, files) + elif path.isfile(): + _load_file(path, files) + return files + +def _load_file(path: Path, files: Dict[Path, List[str]]): + try: + files[path] = path.lines('shift_jis_2004') + except UnicodeDecodeError: + pass \ No newline at end of file diff --git a/jubeatools/formats/jubeat_analyser/memo/__init__.py b/jubeatools/formats/jubeat_analyser/memo/__init__.py new file mode 100644 index 0000000..de673e7 --- /dev/null +++ b/jubeatools/formats/jubeat_analyser/memo/__init__.py @@ -0,0 +1,11 @@ +""" +memo + +#memo is the first (and probably oldest) youbeat-like format jubeat analyser +supports, the japanese docs give a good overview of what it looks like : + +http://yosh52.web.fc2.com/jubeat/fumenformat.html +""" + +from .dump import dump_memo +from .load import load_memo diff --git a/jubeatools/formats/jubeat_analyser/memo/dump.py b/jubeatools/formats/jubeat_analyser/memo/dump.py new file mode 100644 index 0000000..2145a10 --- /dev/null +++ b/jubeatools/formats/jubeat_analyser/memo/dump.py @@ -0,0 +1,2 @@ +def dump_memo(song): + ... \ No newline at end of file diff --git a/jubeatools/formats/jubeat_analyser/memo/load.py b/jubeatools/formats/jubeat_analyser/memo/load.py new file mode 100644 index 0000000..77d4d59 --- /dev/null +++ b/jubeatools/formats/jubeat_analyser/memo/load.py @@ -0,0 +1,43 @@ +from jubeatools.song import Song +from path import Path +from ..parser import JubeatAnalyserParser +from dataclasses import dataclass +from typing import List + + + +class MemoParser(JubeatAnalyserParser): + def __init__(self): + super().__init__() + self.sections: List[MemoLoadedSection] = [] + + def do_memo(self): + ... + + def do_memo1(self): + raise ValueError("This is not a memo file") + + do_boogie = do_memo2 = do_memo1 + + def do_bpp(self, value): + if self.sections: + raise ValueError( + "jubeatools does not handle changing the bytes per panel value halfway" + ) + else: + super().do_bpp(value) + + + def append_chart_line(self, position: str): + 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: + raise SyntaxError(f"Invalid chart line for #bpp=2 : {line}") + self.current_chart_lines.append(line) + +def load_memo(path: Path) -> Song: + # 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 + with open(path, encoding="shift_jis_2004") as f: + lines = f.readlines() \ No newline at end of file diff --git a/jubeatools/formats/jubeat_analyser/mono_column/__init__.py b/jubeatools/formats/jubeat_analyser/mono_column/__init__.py new file mode 100644 index 0000000..673fcf1 --- /dev/null +++ b/jubeatools/formats/jubeat_analyser/mono_column/__init__.py @@ -0,0 +1,26 @@ +""" +Mono column (not an official name) + +It's the format jubeat analyser expects when otherwise no format command has +been found (like #memo #memo1 #memo2 or #boogie) + +Mono-column files are usually properly decoded using `shift_jis_2004` + +Mono-column files are made up of several sections. +Each section is made up of a series of command lines and chart lines. +The section end is marked by a line starting with "--" + +Command lines follow the usual jubeat analyser command pattern + +Chart lines come in groups of 4 in each section. +A group of 4 chart lines makes up a frame, which contains the note symbols. + +Note symbols encode both the position and the time. The position is determined +"visually" by the position the note symbol occupies in the frame. +The time within the frame is determined by the precise symbol used. + +The default note symbols are the circled numbers from ① to ⑯, they represent +""" + +from .dump import dump_mono_column +from .load import load_mono_column diff --git a/jubeatools/formats/memo/mono_column/dump.py b/jubeatools/formats/jubeat_analyser/mono_column/dump.py similarity index 99% rename from jubeatools/formats/memo/mono_column/dump.py rename to jubeatools/formats/jubeat_analyser/mono_column/dump.py index 8d7c585..51eecbf 100644 --- a/jubeatools/formats/memo/mono_column/dump.py +++ b/jubeatools/formats/jubeat_analyser/mono_column/dump.py @@ -23,7 +23,7 @@ from jubeatools.song import ( ) from ..command import dump_command -from .commons import CIRCLE_FREE_SYMBOLS, NOTE_SYMBOLS +from ..symbols import CIRCLE_FREE_SYMBOLS, NOTE_SYMBOLS COMMAND_ORDER = [ "b", diff --git a/jubeatools/formats/memo/mono_column/load.py b/jubeatools/formats/jubeat_analyser/mono_column/load.py similarity index 78% rename from jubeatools/formats/memo/mono_column/load.py rename to jubeatools/formats/jubeat_analyser/mono_column/load.py index 0620043..2ed270b 100644 --- a/jubeatools/formats/memo/mono_column/load.py +++ b/jubeatools/formats/jubeat_analyser/mono_column/load.py @@ -7,6 +7,7 @@ from decimal import Decimal from enum import Enum from itertools import product from typing import Dict, Iterator, List, Set, Tuple +from functools import reduce import constraint from parsimonious import Grammar, NodeVisitor, ParseError @@ -27,8 +28,10 @@ from jubeatools.song import ( ) from ..command import is_command, parse_command -from ..symbol import is_symbol_definition, parse_symbol_definition -from .commons import CIRCLE_FREE_SYMBOLS, NOTE_SYMBOLS +from ..files import load_files +from ..parser import JubeatAnalyserParser +from ..symbol_definition import is_symbol_definition, parse_symbol_definition +from ..symbols import CIRCLE_FREE_SYMBOLS, NOTE_SYMBOLS mono_column_chart_line_grammar = Grammar( r""" @@ -211,127 +214,23 @@ def decimal_to_beats(current_beat: Decimal, symbol_timing: Decimal) -> BeatsTime return BeatsTime(decimal_time).limit_denominator(240) -class MonoColumnParser: +class MonoColumnParser(JubeatAnalyserParser): def __init__(self): - self.music = None - self.symbols = deepcopy(SYMBOL_TO_DECIMAL_TIME) - self.current_beat = Decimal("0") - self.current_tempo = None - self.current_chart_lines = [] - self.timing_events = [] - self.offset = 0 - self.beats_per_section = 4 - self.bytes_per_panel = 2 - self.level = 1 - self.difficulty = None - self.title = None - self.artist = None - self.jacket = None - self.preview_start = None - self.hold_by_arrow = False - self.circle_free = False + super().__init__() self.sections: List[MonoColumnLoadedSection] = [] - def handle_command(self, command, value=None): - try: - method = getattr(self, f"do_{command}") - except AttributeError: - raise SyntaxError(f"Unknown analyser command : {command}") from None - - if value is not None: - method(value) - else: - method() - - def do_m(self, value): - self.music = value - - def do_t(self, value): - self.current_tempo = Decimal(value) - self.timing_events.append(BPMEvent(self.current_beat, BPM=self.current_tempo)) - - def do_o(self, value): - self.offset = int(value) - - def do_b(self, value): - self.beats_per_section = Decimal(value) - def do_memo(self): raise ValueError("This is not a mono-column file") do_boogie = do_memo2 = do_memo1 = do_memo - def do_pw(self, value): - if int(value) != 4: - raise ValueError("jubeatools only supports 4×4 charts") - - do_ph = do_pw - - def do_lev(self, value): - self.level = int(value) - - def do_dif(self, value): - dif = int(value) - if dif <= 0: - raise ValueError(f"Unknown chart difficulty : {dif}") - if dif < 4: - self.difficulty = DIFFICULTIES[dif] - else: - self.difficulty = f"EDIT-{dif-3}" - - def do_title(self, value): - self.title = value - - def do_artist(self, value): - self.artist = value - - def do_jacket(self, value): - self.jacket = value - - def do_prevpos(self, value): - self.preview_start = int(value) - def do_bpp(self, value): - bpp = int(value) if self.sections: raise ValueError( "jubeatools does not handle changing the bytes per panel value halfway" ) - elif bpp not in (1, 2): - raise ValueError(f"Unexcpected bpp value : {value}") - elif self.circle_free and bpp == 1: - raise ValueError("#bpp can only be 2 when #circlefree is activated") else: - self.bytes_per_panel = int(value) - - def do_holdbyarrow(self, value): - self.hold_by_arrow = int(value) == 1 - - def do_holdbytilde(self, value): - if int(value): - raise ValueError("jubeatools does not support #holdbytilde") - - def do_circlefree(self, raw_value): - activate = bool(int(raw_value)) - if activate and self.bytes_per_panel != 2: - raise ValueError("#circlefree can only be activated when #bpp=2") - self.circle_free = activate - - def define_symbol(self, symbol: str, timing: Decimal): - bpp = self.bytes_per_panel - 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 " - f"should be {bpp} bytes long but '{symbol}' is {length_as_shift_jis}" - ) - if timing > self.beats_per_section: - message = ( - "Invalid symbol definition conscidering the number of beats per section :\n" - f"*{symbol}:{timing}" - ) - raise ValueError(message) - self.symbols[symbol] = timing + super().do_bpp(value) def move_to_next_section(self): if len(self.current_chart_lines) % 4 != 0: @@ -369,7 +268,7 @@ class MonoColumnParser: elif is_separator(line): self.move_to_next_section() elif not is_empty_line(line): - raise SyntaxError(f"not a valid #memo line : {line}") + raise SyntaxError(f"not a valid mono-column file line : {line}") def notes(self) -> Iterator[Union[TapNote, LongNote]]: if self.hold_by_arrow: @@ -500,12 +399,15 @@ class MonoColumnParser: def load_mono_column(path: Path) -> Song: - # 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 - with open(path, encoding="shift_jis_2004") as f: - lines = f.readlines() + files = load_files(path) + charts = [ + _load_mono_column_file(lines) + for _, lines in files.items() + ] + return reduce(lambda a, b: a.merge(b), charts) + +def _load_mono_column_file(lines: List[str]) -> Song: state = MonoColumnParser() for i, raw_line in enumerate(lines): try: diff --git a/jubeatools/formats/jubeat_analyser/parser.py b/jubeatools/formats/jubeat_analyser/parser.py new file mode 100644 index 0000000..a5ac351 --- /dev/null +++ b/jubeatools/formats/jubeat_analyser/parser.py @@ -0,0 +1,124 @@ +"""Base class and tools for the different parsers""" +from copy import deepcopy +from .symbols import NOTE_SYMBOLS +from decimal import Decimal +from jubeatools.song import BPMEvent + +DIFFICULTIES = {1: "BSC", 2: "ADV", 3: "EXT"} + +SYMBOL_TO_DECIMAL_TIME = { + symbol: Decimal("0.25") * index for index, symbol in enumerate(NOTE_SYMBOLS) +} + + +class JubeatAnalyserParser: + def __init__(self): + self.music = None + self.symbols = deepcopy(SYMBOL_TO_DECIMAL_TIME) + self.current_beat = Decimal("0") + self.current_tempo = None + self.current_chart_lines = [] + self.timing_events = [] + self.offset = 0 + self.beats_per_section = 4 + self.bytes_per_panel = 2 + self.level = 1 + self.difficulty = None + self.title = None + self.artist = None + self.jacket = None + self.preview_start = None + self.hold_by_arrow = False + self.circle_free = False + + def handle_command(self, command, value=None): + try: + method = getattr(self, f"do_{command}") + except AttributeError: + raise SyntaxError(f"Unknown analyser command : {command}") from None + + if value is not None: + method(value) + else: + method() + + def do_m(self, value): + self.music = value + + def do_t(self, value): + self.current_tempo = Decimal(value) + self.timing_events.append(BPMEvent(self.current_beat, BPM=self.current_tempo)) + + def do_o(self, value): + self.offset = int(value) + + def do_b(self, value): + self.beats_per_section = Decimal(value) + + def do_pw(self, value): + if int(value) != 4: + raise ValueError("jubeatools only supports 4×4 charts") + + do_ph = do_pw + + def do_lev(self, value): + self.level = int(value) + + def do_dif(self, value): + dif = int(value) + if dif <= 0: + raise ValueError(f"Unknown chart difficulty : {dif}") + if dif < 4: + self.difficulty = DIFFICULTIES[dif] + else: + self.difficulty = f"EDIT-{dif-3}" + + def do_title(self, value): + self.title = value + + def do_artist(self, value): + self.artist = value + + def do_jacket(self, value): + self.jacket = value + + def do_prevpos(self, value): + self.preview_start = int(value) + + def do_bpp(self, value): + bpp = int(value) + if bpp not in (1, 2): + raise ValueError(f"Unexcpected bpp value : {value}") + elif self.circle_free and bpp == 1: + raise ValueError("#bpp can only be 2 when #circlefree is activated") + else: + self.bytes_per_panel = int(value) + + def do_holdbyarrow(self, value): + self.hold_by_arrow = int(value) == 1 + + def do_holdbytilde(self, value): + if int(value): + raise ValueError("jubeatools does not support #holdbytilde") + + def do_circlefree(self, raw_value): + activate = bool(int(raw_value)) + if activate and self.bytes_per_panel != 2: + raise ValueError("#circlefree can only be activated when #bpp=2") + self.circle_free = activate + + def define_symbol(self, symbol: str, timing: Decimal): + bpp = self.bytes_per_panel + 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 " + f"should be {bpp} bytes long but '{symbol}' is {length_as_shift_jis}" + ) + if timing > self.beats_per_section: + message = ( + "Invalid symbol definition conscidering the number of beats per section :\n" + f"*{symbol}:{timing}" + ) + raise ValueError(message) + self.symbols[symbol] = timing \ No newline at end of file diff --git a/jubeatools/formats/memo/symbol.py b/jubeatools/formats/jubeat_analyser/symbol_definition.py similarity index 98% rename from jubeatools/formats/memo/symbol.py rename to jubeatools/formats/jubeat_analyser/symbol_definition.py index 195d4e2..41909f9 100644 --- a/jubeatools/formats/memo/symbol.py +++ b/jubeatools/formats/jubeat_analyser/symbol_definition.py @@ -1,5 +1,5 @@ """ -Beat symbol definition +Note symbol definition """ from decimal import Decimal from typing import Optional, Tuple diff --git a/jubeatools/formats/memo/mono_column/commons.py b/jubeatools/formats/jubeat_analyser/symbols.py similarity index 96% rename from jubeatools/formats/memo/mono_column/commons.py rename to jubeatools/formats/jubeat_analyser/symbols.py index 1aa89b7..84b64be 100644 --- a/jubeatools/formats/memo/mono_column/commons.py +++ b/jubeatools/formats/jubeat_analyser/symbols.py @@ -1,3 +1,5 @@ +"""Usual symbols for memo files""" + NOTE_SYMBOLS = [ "①", "②", diff --git a/jubeatools/formats/memo/tests/__init__.py b/jubeatools/formats/jubeat_analyser/tests/__init__.py similarity index 100% rename from jubeatools/formats/memo/tests/__init__.py rename to jubeatools/formats/jubeat_analyser/tests/__init__.py diff --git a/jubeatools/formats/jubeat_analyser/tests/test_memo.py b/jubeatools/formats/jubeat_analyser/tests/test_memo.py new file mode 100644 index 0000000..e85a0d8 --- /dev/null +++ b/jubeatools/formats/jubeat_analyser/tests/test_memo.py @@ -0,0 +1,15 @@ +from decimal import Decimal + +from hypothesis import given + +from jubeatools.song import BeatsTime, BPMEvent, Chart, Metadata, SecondsTime, Timing +from jubeatools.testutils.strategies import NoteOption +from jubeatools.testutils.strategies import notes as notes_strat + +from ..mono_column.dump import _dump_mono_column_chart +from ..mono_column.load import MonoColumnParser + + +@given(notes_strat(NoteOption.LONGS)) +def test_many_notes(notes): + ... \ No newline at end of file diff --git a/jubeatools/formats/memo/tests/test_mono_column.py b/jubeatools/formats/jubeat_analyser/tests/test_mono_column.py similarity index 100% rename from jubeatools/formats/memo/tests/test_mono_column.py rename to jubeatools/formats/jubeat_analyser/tests/test_mono_column.py diff --git a/jubeatools/formats/memo/tests/test_mono_column_hypothesis.py b/jubeatools/formats/jubeat_analyser/tests/test_mono_column_hypothesis.py similarity index 64% rename from jubeatools/formats/memo/tests/test_mono_column_hypothesis.py rename to jubeatools/formats/jubeat_analyser/tests/test_mono_column_hypothesis.py index bbfdf25..38a4be4 100644 --- a/jubeatools/formats/memo/tests/test_mono_column_hypothesis.py +++ b/jubeatools/formats/jubeat_analyser/tests/test_mono_column_hypothesis.py @@ -4,7 +4,9 @@ import hypothesis.strategies as st from hypothesis import given from jubeatools.song import BeatsTime, BPMEvent, Chart, Metadata, SecondsTime, Timing -from jubeatools.testutils.strategies import long_note, tap_note +from jubeatools.testutils.strategies import NoteOption, long_note +from jubeatools.testutils.strategies import notes as notes_strat +from jubeatools.testutils.strategies import tap_note from ..mono_column.dump import _dump_mono_column_chart from ..mono_column.load import MonoColumnParser @@ -42,3 +44,21 @@ def test_single_long_note(note): parser.load_line(line) actual = set(parser.notes()) assert set([note]) == actual + + +@given(notes_strat(NoteOption.LONGS)) +def test_many_notes(notes): + timing = Timing( + events=[BPMEvent(BeatsTime(0), Decimal(120))], beat_zero_offset=SecondsTime(0) + ) + chart = Chart( + level=0, timing=timing, notes=sorted(notes, key=lambda n: (n.time, n.position)) + ) + metadata = Metadata("", "", "", "") + string_io = _dump_mono_column_chart("", chart, metadata, timing) + chart = string_io.getvalue() + parser = MonoColumnParser() + for line in chart.split("\n"): + parser.load_line(line) + actual = set(parser.notes()) + assert notes == actual diff --git a/jubeatools/formats/memo/__init__.py b/jubeatools/formats/memo/__init__.py deleted file mode 100644 index 5c3f116..0000000 --- a/jubeatools/formats/memo/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -memo is a vague term refering to several legacy formats. -They were originally derived from the (somewhat) human-readable format choosen -by websites storing official jubeat charts in text form as a memory aid. - -The machine-readable variants are partially documented (in japanese) -on these pages : -- http://yosh52.web.fc2.com/jubeat/fumenformat.html -- http://yosh52.web.fc2.com/jubeat/holdmarker.html - -Known simple commands : - - b= : beats per measure (4 by default) - - m="" : music file path - - o= : offset in ms (100 by default) - - r= : increase the offset (in ms) - - t= : tempo - -Known hash commands : - - #memo # mono-column format - - #memo1 # youbeat-like but missing a lot of the youbeat features - - #memo2 # youbeat-like memo - - #boogie # youbeat - - #pw= # number of panels horizontally (4 by default) - - #ph= # number of panels vertically (4 by default) - - #lev= # chart level (typically 1 to 10) - - #dif={1, 2, 3} # 1: BSC, 2: ADV, 3: EXT - - #title="" # music title - - #artist="" # artist's name - - #jacket="" # music cover art path - - #prevpos= # preview start (in ms) - - #bpp # bytes per panel (2 by default) -""" diff --git a/jubeatools/formats/memo/mono_column/__init__.py b/jubeatools/formats/memo/mono_column/__init__.py deleted file mode 100644 index 688f4b7..0000000 --- a/jubeatools/formats/memo/mono_column/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .dump import dump_mono_column -from .load import load_mono_column diff --git a/jubeatools/song.py b/jubeatools/song.py index 3d5cbe6..f3a0770 100644 --- a/jubeatools/song.py +++ b/jubeatools/song.py @@ -166,3 +166,23 @@ class Song: metadata: Metadata charts: Mapping[str, Chart] = field(default_factory=MultiDict) global_timing: Optional[Timing] = None + + def merge(self, other: Song) -> Song: + if self.metadata != other.metadata: + raise ValueError( + "Merge conflit in song metadata :\n" + f"{self.metadata}\n" + f"{other.metadata}" + ) + charts = MultiDict() + charts.extend(self.charts) + charts.extend(other.charts) + if ( + self.global_timing is not None + and other.global_timing is not None + and self.global_timing != other.global_timing + ): + raise ValueError("Can't merge songs with differing global timings") + global_timing = self.global_timing or other.global_timing + return Song(self.metadata, charts, global_timing) + diff --git a/jubeatools/testutils/strategies.py b/jubeatools/testutils/strategies.py index 001adc3..54f146f 100644 --- a/jubeatools/testutils/strategies.py +++ b/jubeatools/testutils/strategies.py @@ -94,7 +94,7 @@ def bad_notes(draw, longs: bool): note_strat = tap_note() if longs: note_strat = st.one_of(note_strat, long_note()) - return draw(st.sets(note_strat)) + return draw(st.sets(note_strat, max_size=50)) @st.composite @@ -105,7 +105,7 @@ def notes(draw, options: NoteOption): note_strat = tap_note() if NoteOption.LONGS in options: note_strat = st.one_of(note_strat, long_note()) - raw_notes = draw(st.sets(note_strat)) + raw_notes = draw(st.sets(note_strat, max_size=50)) if NoteOption.COLLISIONS in options: return raw_notes diff --git a/poetry.lock b/poetry.lock index cb0295b..286976b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -72,7 +72,7 @@ description = "A library for property-based testing" name = "hypothesis" optional = false python-versions = ">=3.5.2" -version = "5.16.3" +version = "5.19.0" [package.dependencies] attrs = ">=19.2.0" @@ -109,12 +109,12 @@ description = "A lightweight library for converting complex datatypes to and fro name = "marshmallow" optional = false python-versions = ">=3.5" -version = "3.6.0" +version = "3.6.1" [package.extras] -dev = ["pytest", "pytz", "simplejson", "mypy (0.770)", "flake8 (3.7.9)", "flake8-bugbear (20.1.4)", "pre-commit (>=1.20,<3.0)", "tox"] -docs = ["sphinx (3.0.3)", "sphinx-issues (1.2.0)", "alabaster (0.7.12)", "sphinx-version-warning (1.1.2)"] -lint = ["mypy (0.770)", "flake8 (3.7.9)", "flake8-bugbear (20.1.4)", "pre-commit (>=1.20,<3.0)"] +dev = ["pytest", "pytz", "simplejson", "mypy (0.770)", "flake8 (3.8.2)", "flake8-bugbear (20.1.4)", "pre-commit (>=2.4,<3.0)", "tox"] +docs = ["sphinx (3.0.4)", "sphinx-issues (1.2.0)", "alabaster (0.7.12)", "sphinx-version-warning (1.1.2)", "autodocsumm (0.1.13)"] +lint = ["mypy (0.770)", "flake8 (3.8.2)", "flake8-bugbear (20.1.4)", "pre-commit (>=2.4,<3.0)"] tests = ["pytest", "pytz", "simplejson"] [[package]] @@ -217,7 +217,7 @@ description = "library with cross-python path, ini-parsing, io, code, log facili name = "py" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.8.1" +version = "1.9.0" [[package]] category = "dev" @@ -233,7 +233,7 @@ description = "pytest: simple powerful testing with Python" name = "pytest" optional = false python-versions = ">=3.5" -version = "5.4.2" +version = "5.4.3" [package.dependencies] atomicwrites = ">=1.0" @@ -267,7 +267,7 @@ description = "Alternative regular expression module, to replace re." name = "regex" optional = false python-versions = "*" -version = "2020.5.14" +version = "2020.6.8" [[package]] category = "dev" @@ -330,11 +330,11 @@ version = "3.7.4.2" [[package]] category = "dev" -description = "Measures number of Terminal column cells of wide-character codes" +description = "Measures the displayed width of unicode strings in a terminal" name = "wcwidth" optional = false python-versions = "*" -version = "0.1.9" +version = "0.2.5" [metadata] content-hash = "ea32264fbb53166d279acff6d1adda6bbf3892b58d1c1ccebe0efabd8b661959" @@ -366,16 +366,16 @@ colorama = [ {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, ] hypothesis = [ - {file = "hypothesis-5.16.3-py3-none-any.whl", hash = "sha256:7e8e0b7d412cb758c662dcbdcdc93cb8dccc38a9c67f2cd4bf885ba9f714f8d4"}, - {file = "hypothesis-5.16.3.tar.gz", hash = "sha256:81f9a033900ea73c7b594173dbce7b9eb39222c04fc6278a6f33cc39c49b144a"}, + {file = "hypothesis-5.19.0-py3-none-any.whl", hash = "sha256:dd21b1be951fefc9022047824c262f4e88d95dd24141b837b92e235c63baabb7"}, + {file = "hypothesis-5.19.0.tar.gz", hash = "sha256:ba7c92006716aaee4684f7876c116adedcfb88b19fcb55d21c47b28f03f933bf"}, ] isort = [ {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"}, ] marshmallow = [ - {file = "marshmallow-3.6.0-py2.py3-none-any.whl", hash = "sha256:f88fe96434b1f0f476d54224d59333eba8ca1a203a2695683c1855675c4049a7"}, - {file = "marshmallow-3.6.0.tar.gz", hash = "sha256:c2673233aa21dde264b84349dc2fd1dce5f30ed724a0a00e75426734de5b84ab"}, + {file = "marshmallow-3.6.1-py2.py3-none-any.whl", hash = "sha256:9aa20f9b71c992b4782dad07c51d92884fd0f7c5cb9d3c737bea17ec1bad765f"}, + {file = "marshmallow-3.6.1.tar.gz", hash = "sha256:35ee2fb188f0bd9fc1cf9ac35e45fd394bd1c153cee430745a465ea435514bd5"}, ] more-itertools = [ {file = "more-itertools-8.4.0.tar.gz", hash = "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5"}, @@ -440,42 +440,42 @@ pluggy = [ {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] py = [ - {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, - {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, + {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"}, + {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, ] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pytest = [ - {file = "pytest-5.4.2-py3-none-any.whl", hash = "sha256:95c710d0a72d91c13fae35dce195633c929c3792f54125919847fdcdf7caa0d3"}, - {file = "pytest-5.4.2.tar.gz", hash = "sha256:eb2b5e935f6a019317e455b6da83dd8650ac9ffd2ee73a7b657a30873d67a698"}, + {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, + {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, ] python-constraint = [ {file = "python-constraint-1.4.0.tar.bz2", hash = "sha256:501d6f17afe0032dfc6ea6c0f8acc12e44f992733f00e8538961031ef27ccb8e"}, ] regex = [ - {file = "regex-2020.5.14-cp27-cp27m-win32.whl", hash = "sha256:e565569fc28e3ba3e475ec344d87ed3cd8ba2d575335359749298a0899fe122e"}, - {file = "regex-2020.5.14-cp27-cp27m-win_amd64.whl", hash = "sha256:d466967ac8e45244b9dfe302bbe5e3337f8dc4dec8d7d10f5e950d83b140d33a"}, - {file = "regex-2020.5.14-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:27ff7325b297fb6e5ebb70d10437592433601c423f5acf86e5bc1ee2919b9561"}, - {file = "regex-2020.5.14-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ea55b80eb0d1c3f1d8d784264a6764f931e172480a2f1868f2536444c5f01e01"}, - {file = "regex-2020.5.14-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:c9bce6e006fbe771a02bda468ec40ffccbf954803b470a0345ad39c603402577"}, - {file = "regex-2020.5.14-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:d881c2e657c51d89f02ae4c21d9adbef76b8325fe4d5cf0e9ad62f850f3a98fd"}, - {file = "regex-2020.5.14-cp36-cp36m-win32.whl", hash = "sha256:99568f00f7bf820c620f01721485cad230f3fb28f57d8fbf4a7967ec2e446994"}, - {file = "regex-2020.5.14-cp36-cp36m-win_amd64.whl", hash = "sha256:70c14743320a68c5dac7fc5a0f685be63bc2024b062fe2aaccc4acc3d01b14a1"}, - {file = "regex-2020.5.14-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a7c37f048ec3920783abab99f8f4036561a174f1314302ccfa4e9ad31cb00eb4"}, - {file = "regex-2020.5.14-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:89d76ce33d3266173f5be80bd4efcbd5196cafc34100fdab814f9b228dee0fa4"}, - {file = "regex-2020.5.14-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:51f17abbe973c7673a61863516bdc9c0ef467407a940f39501e786a07406699c"}, - {file = "regex-2020.5.14-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ce5cc53aa9fbbf6712e92c7cf268274eaff30f6bd12a0754e8133d85a8fb0f5f"}, - {file = "regex-2020.5.14-cp37-cp37m-win32.whl", hash = "sha256:8044d1c085d49673aadb3d7dc20ef5cb5b030c7a4fa253a593dda2eab3059929"}, - {file = "regex-2020.5.14-cp37-cp37m-win_amd64.whl", hash = "sha256:c2062c7d470751b648f1cacc3f54460aebfc261285f14bc6da49c6943bd48bdd"}, - {file = "regex-2020.5.14-cp38-cp38-manylinux1_i686.whl", hash = "sha256:329ba35d711e3428db6b45a53b1b13a0a8ba07cbbcf10bbed291a7da45f106c3"}, - {file = "regex-2020.5.14-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:579ea215c81d18da550b62ff97ee187b99f1b135fd894a13451e00986a080cad"}, - {file = "regex-2020.5.14-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:3a9394197664e35566242686d84dfd264c07b20f93514e2e09d3c2b3ffdf78fe"}, - {file = "regex-2020.5.14-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ce367d21f33e23a84fb83a641b3834dd7dd8e9318ad8ff677fbfae5915a239f7"}, - {file = "regex-2020.5.14-cp38-cp38-win32.whl", hash = "sha256:1386e75c9d1574f6aa2e4eb5355374c8e55f9aac97e224a8a5a6abded0f9c927"}, - {file = "regex-2020.5.14-cp38-cp38-win_amd64.whl", hash = "sha256:7e61be8a2900897803c293247ef87366d5df86bf701083b6c43119c7c6c99108"}, - {file = "regex-2020.5.14.tar.gz", hash = "sha256:ce450ffbfec93821ab1fea94779a8440e10cf63819be6e176eb1973a6017aff5"}, + {file = "regex-2020.6.8-cp27-cp27m-win32.whl", hash = "sha256:fbff901c54c22425a5b809b914a3bfaf4b9570eee0e5ce8186ac71eb2025191c"}, + {file = "regex-2020.6.8-cp27-cp27m-win_amd64.whl", hash = "sha256:112e34adf95e45158c597feea65d06a8124898bdeac975c9087fe71b572bd938"}, + {file = "regex-2020.6.8-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:92d8a043a4241a710c1cf7593f5577fbb832cf6c3a00ff3fc1ff2052aff5dd89"}, + {file = "regex-2020.6.8-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bae83f2a56ab30d5353b47f9b2a33e4aac4de9401fb582b55c42b132a8ac3868"}, + {file = "regex-2020.6.8-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:b2ba0f78b3ef375114856cbdaa30559914d081c416b431f2437f83ce4f8b7f2f"}, + {file = "regex-2020.6.8-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:95fa7726d073c87141f7bbfb04c284901f8328e2d430eeb71b8ffdd5742a5ded"}, + {file = "regex-2020.6.8-cp36-cp36m-win32.whl", hash = "sha256:e3cdc9423808f7e1bb9c2e0bdb1c9dc37b0607b30d646ff6faf0d4e41ee8fee3"}, + {file = "regex-2020.6.8-cp36-cp36m-win_amd64.whl", hash = "sha256:c78e66a922de1c95a208e4ec02e2e5cf0bb83a36ceececc10a72841e53fbf2bd"}, + {file = "regex-2020.6.8-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:08997a37b221a3e27d68ffb601e45abfb0093d39ee770e4257bd2f5115e8cb0a"}, + {file = "regex-2020.6.8-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2f6f211633ee8d3f7706953e9d3edc7ce63a1d6aad0be5dcee1ece127eea13ae"}, + {file = "regex-2020.6.8-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:55b4c25cbb3b29f8d5e63aeed27b49fa0f8476b0d4e1b3171d85db891938cc3a"}, + {file = "regex-2020.6.8-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:89cda1a5d3e33ec9e231ece7307afc101b5217523d55ef4dc7fb2abd6de71ba3"}, + {file = "regex-2020.6.8-cp37-cp37m-win32.whl", hash = "sha256:690f858d9a94d903cf5cada62ce069b5d93b313d7d05456dbcd99420856562d9"}, + {file = "regex-2020.6.8-cp37-cp37m-win_amd64.whl", hash = "sha256:1700419d8a18c26ff396b3b06ace315b5f2a6e780dad387e4c48717a12a22c29"}, + {file = "regex-2020.6.8-cp38-cp38-manylinux1_i686.whl", hash = "sha256:654cb773b2792e50151f0e22be0f2b6e1c3a04c5328ff1d9d59c0398d37ef610"}, + {file = "regex-2020.6.8-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:52e1b4bef02f4040b2fd547357a170fc1146e60ab310cdbdd098db86e929b387"}, + {file = "regex-2020.6.8-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:cf59bbf282b627130f5ba68b7fa3abdb96372b24b66bdf72a4920e8153fc7910"}, + {file = "regex-2020.6.8-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:5aaa5928b039ae440d775acea11d01e42ff26e1561c0ffcd3d805750973c6baf"}, + {file = "regex-2020.6.8-cp38-cp38-win32.whl", hash = "sha256:97712e0d0af05febd8ab63d2ef0ab2d0cd9deddf4476f7aa153f76feef4b2754"}, + {file = "regex-2020.6.8-cp38-cp38-win_amd64.whl", hash = "sha256:6ad8663c17db4c5ef438141f99e291c4d4edfeaacc0ce28b5bba2b0bf273d9b5"}, + {file = "regex-2020.6.8.tar.gz", hash = "sha256:e9b64e609d37438f7d6e68c2546d2cb8062f3adb27e6336bc129b51be20773ac"}, ] rope = [ {file = "rope-0.17.0.tar.gz", hash = "sha256:658ad6705f43dcf3d6df379da9486529cf30e02d9ea14c5682aa80eb33b649e1"}, @@ -551,6 +551,6 @@ typing-extensions = [ {file = "typing_extensions-3.7.4.2.tar.gz", hash = "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae"}, ] wcwidth = [ - {file = "wcwidth-0.1.9-py2.py3-none-any.whl", hash = "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1"}, - {file = "wcwidth-0.1.9.tar.gz", hash = "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"}, + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, ] diff --git a/pyproject.toml b/pyproject.toml index 36b11ba..9cab35e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,14 @@ isort = "^4.3.21" [tool.poetry.scripts] jubeatools = 'jubeatools.cli:convert' +[tool.isort] +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +ensure_newline_before_comments = true +line_length = 88 + [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api"