1
0
mirror of synced 2025-02-23 05:29:46 +01:00

File reorg.

This commit is contained in:
Stepland 2020-07-08 16:07:15 +02:00
parent 0724f4a4e1
commit 818deeae56
23 changed files with 396 additions and 197 deletions

View File

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

View File

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

View File

@ -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=<decimal> : beats per measure (4 by default)
- m="<path>" : music file path
- o=<int> : offset in ms (100 by default)
- r=<int> : ? increase the offset ? (in ms) (not supported, couldn't find any examples, wtf is it for ?)
- t=<decimal> : 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=<int> # number of panels horizontally (4 by default)
- #ph=<int> # number of panels vertically (4 by default)
- #lev=<int> # chart level (typically 1 to 10)
- #dif={1, 2, 3} # 1: BSC, 2: ADV, 3: EXT
- #title="<str>" # music title
- #artist="<str>" # artist's name
- #jacket="<path>" # music cover art path
- #prevpos=<int> # 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

View File

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

View File

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

View File

@ -0,0 +1,2 @@
def dump_memo(song):
...

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
"""
Beat symbol definition
Note symbol definition
"""
from decimal import Decimal
from typing import Optional, Tuple

View File

@ -1,3 +1,5 @@
"""Usual symbols for memo files"""
NOTE_SYMBOLS = [
"",
"",

View File

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

View File

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

View File

@ -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=<decimal> : beats per measure (4 by default)
- m="<path>" : music file path
- o=<int> : offset in ms (100 by default)
- r=<int> : increase the offset (in ms)
- t=<decimal> : 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=<int> # number of panels horizontally (4 by default)
- #ph=<int> # number of panels vertically (4 by default)
- #lev=<int> # chart level (typically 1 to 10)
- #dif={1, 2, 3} # 1: BSC, 2: ADV, 3: EXT
- #title="<str>" # music title
- #artist="<str>" # artist's name
- #jacket="<path>" # music cover art path
- #prevpos=<int> # preview start (in ms)
- #bpp # bytes per panel (2 by default)
"""

View File

@ -1,2 +0,0 @@
from .dump import dump_mono_column
from .load import load_mono_column

View File

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

View File

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

82
poetry.lock generated
View File

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

View File

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