diff --git a/CHANGELOG.md b/CHANGELOG.md index a0e1dd8..7fb650d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,12 @@ each version Format based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -I try to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html), -sometimes. +I try to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## v2.0.1 +### Fixed +- `guess_format()` would raise an exception when called on a specially crafted + empty jbsq file, not anymore ! ## v2.0.0 ### BREAKING CHANGE diff --git a/jubeatools/formats/guess.py b/jubeatools/formats/guess.py index 4825d80..9c41f5b 100644 --- a/jubeatools/formats/guess.py +++ b/jubeatools/formats/guess.py @@ -1,6 +1,8 @@ import json import re +from functools import wraps from pathlib import Path +from typing import Any, Callable, Type from .format_names import Format @@ -108,17 +110,29 @@ def recognize_jubeat_analyser_format(path: Path) -> Format: raise ValueError("Unrecognized file format") +def false_if_raises( + *exceptions: Type[Exception], +) -> Callable[[Callable[..., bool]], Callable[..., bool]]: + def decorator(f: Callable[..., bool]) -> Callable[..., bool]: + @wraps(f) + def wrapper(*a: Any, **kw: Any) -> bool: + try: + return f(*a, **kw) + except Exception as e: + if exceptions and not isinstance(e, exceptions): + raise + else: + return False + + return wrapper + + return decorator + + +@false_if_raises(UnicodeDecodeError, StopIteration) def looks_like_eve(path: Path) -> bool: with path.open(encoding="ascii") as f: - try: - line = f.readline() - except UnicodeDecodeError: - return False - - if line.strip(): - return looks_like_eve_line(next(f)) - - return False + return looks_like_eve_line(f.readline()) EVE_COMMANDS = { @@ -154,5 +168,6 @@ def looks_like_eve_line(line: str) -> bool: def looks_like_jbsq(path: Path) -> bool: - magic = path.open(mode="rb").read(4) - return magic in (b"IJBQ", b"IJSQ", b"JBSQ") + with path.open(mode="rb") as f: + magic = f.read(4) + return magic in (b"IJBQ", b"IJSQ", b"JBSQ") diff --git a/jubeatools/formats/konami/jbsq/tests/__init__.py b/jubeatools/formats/konami/jbsq/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/jubeatools/formats/konami/jbsq/tests/example1.py b/jubeatools/formats/konami/jbsq/tests/example1.py new file mode 100644 index 0000000..8cad1d6 --- /dev/null +++ b/jubeatools/formats/konami/jbsq/tests/example1.py @@ -0,0 +1,23 @@ +from decimal import Decimal +from fractions import Fraction + +from jubeatools.song import * + +data = Song( + metadata=Metadata( + title=None, artist=None, audio=None, cover=None, preview=None, preview_file=None + ), + charts={ + "BSC": Chart( + level=Decimal("0"), + timing=Timing( + events=(BPMEvent(time=Fraction(0, 1), BPM=Decimal("288.11")),), + beat_zero_offset=Decimal("0.04"), + ), + hakus=set(), + notes=[], + ) + }, + common_timing=None, + common_hakus=None, +) diff --git a/jubeatools/formats/konami/jbsq/test_jbsq.py b/jubeatools/formats/konami/jbsq/tests/test_jbsq.py similarity index 90% rename from jubeatools/formats/konami/jbsq/test_jbsq.py rename to jubeatools/formats/konami/jbsq/tests/test_jbsq.py index 497aa47..d34c259 100644 --- a/jubeatools/formats/konami/jbsq/test_jbsq.py +++ b/jubeatools/formats/konami/jbsq/tests/test_jbsq.py @@ -1,13 +1,14 @@ from enum import Enum -from hypothesis import given +from hypothesis import example, given from jubeatools import song from jubeatools.formats import Format from jubeatools.formats.konami.testutils import eve_compatible_song from jubeatools.testutils.test_patterns import dump_and_load, dump_and_load_then_compare -from .construct import jbsq +from ..construct import jbsq +from . import example1 @given(eve_compatible_song()) @@ -21,6 +22,7 @@ def test_that_full_chart_roundtrips(song: song.Song) -> None: @given(eve_compatible_song()) +@example(example1.data) def test_that_difficulty_name_is_loaded_properly(original_song: song.Song) -> None: recovered_song = dump_and_load( Format.JBSQ, diff --git a/poetry.lock b/poetry.lock index 86cfd12..82849ac 100644 --- a/poetry.lock +++ b/poetry.lock @@ -22,7 +22,7 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (> [[package]] name = "autoflake" -version = "1.5.3" +version = "1.6.0" description = "Removes unused imports and unused variables" category = "dev" optional = false @@ -30,11 +30,11 @@ python-versions = ">=3.7" [package.dependencies] pyflakes = ">=1.1.0" -toml = ">=0.10.2" +tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} [[package]] name = "autoimport" -version = "1.2.2" +version = "1.2.3" description = "Autoimport missing python libraries." category = "dev" optional = false @@ -137,7 +137,7 @@ pyflakes = ">=2.3.0,<2.4.0" [[package]] name = "hypothesis" -version = "6.54.5" +version = "6.54.6" description = "A library for property-based testing" category = "dev" optional = false @@ -201,7 +201,7 @@ toml = ">=0.10.2,<0.11.0" [[package]] name = "marshmallow" -version = "3.17.1" +version = "3.18.0" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." category = "main" optional = false @@ -211,9 +211,9 @@ python-versions = ">=3.7" packaging = ">=17.0" [package.extras] -dev = ["pytest", "pytz", "simplejson", "mypy (==0.971)", "flake8 (==5.0.4)", "flake8-bugbear (==22.8.22)", "pre-commit (>=2.4,<3.0)", "tox"] +dev = ["pytest", "pytz", "simplejson", "mypy (==0.971)", "flake8 (==5.0.4)", "flake8-bugbear (==22.9.11)", "pre-commit (>=2.4,<3.0)", "tox"] docs = ["sphinx (==5.1.1)", "sphinx-issues (==3.0.1)", "alabaster (==0.7.12)", "sphinx-version-warning (==1.1.2)", "autodocsumm (==0.2.9)"] -lint = ["mypy (==0.971)", "flake8 (==5.0.4)", "flake8-bugbear (==22.8.22)", "pre-commit (>=2.4,<3.0)"] +lint = ["mypy (==0.971)", "flake8 (==5.0.4)", "flake8-bugbear (==22.9.11)", "pre-commit (>=2.4,<3.0)"] tests = ["pytest", "pytz", "simplejson"] [[package]] @@ -362,7 +362,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pydantic" -version = "1.10.1" +version = "1.10.2" description = "Data validation and settings management using python type hints" category = "dev" optional = false @@ -559,10 +559,7 @@ content-hash = "a94015b3d8ca8a0e27556df0eb4697f7b4765510b89b49466ffc2a1a92174616 atomicwrites = [] attrs = [] autoflake = [] -autoimport = [ - {file = "autoimport-1.2.2-py3-none-any.whl", hash = "sha256:cf8982a621df199eff0e88f3f1190dc4f72093395f6bf478e544fac10217f160"}, - {file = "autoimport-1.2.2.tar.gz", hash = "sha256:ae58d93b1b71529f78bcf06733e60170600620c903740f5745f928cc8f1243f3"}, -] +autoimport = [] black = [] click = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, diff --git a/test b/test new file mode 100644 index 0000000..e69de29