feat: formatter utils

This commit is contained in:
2024-04-03 13:37:23 +08:00
parent c585e5ec04
commit c705fea473
7 changed files with 354 additions and 77 deletions

View File

@ -0,0 +1,2 @@
from .play_result import PlayResultFormatter
from .rating_class import RatingClassFormatter

View File

@ -0,0 +1,143 @@
from typing import Any, Literal, overload
from arcaea_offline.constants.enums import (
ArcaeaPlayResultClearType,
ArcaeaPlayResultModifier,
)
from arcaea_offline.constants.play_result import ScoreLowerLimits
class PlayResultFormatter:
SCORE_GRADE_FORMAT_RESULTS = Literal["EX+", "EX", "AA", "A", "B", "C", "D"]
@staticmethod
def score_grade(score: int) -> SCORE_GRADE_FORMAT_RESULTS:
"""
Returns the score grade, e.g. EX+.
Raises `ValueError` if the score is negative.
"""
if not isinstance(score, int):
raise TypeError(f"Unsupported type {type(score)}, cannot format")
if score >= ScoreLowerLimits.EX_PLUS:
return "EX+"
elif score >= ScoreLowerLimits.EX:
return "EX"
elif score >= ScoreLowerLimits.AA:
return "AA"
elif score >= ScoreLowerLimits.A:
return "A"
elif score >= ScoreLowerLimits.B:
return "B"
elif score >= ScoreLowerLimits.C:
return "C"
elif score >= ScoreLowerLimits.D:
return "D"
else:
raise ValueError("score cannot be negative")
CLEAR_TYPE_FORMAT_RESULTS = Literal[
"TRACK LOST",
"NORMAL CLEAR",
"FULL RECALL",
"PURE MEMORY",
"EASY CLEAR",
"HARD CLEAR",
"UNKNOWN",
"None",
]
@overload
@classmethod
def clear_type(
cls, clear_type: ArcaeaPlayResultClearType
) -> CLEAR_TYPE_FORMAT_RESULTS:
"""
Returns the uppercased clear type name, e.g. NORMAL CLEAR.
"""
...
@overload
@classmethod
def clear_type(cls, clear_type: int) -> CLEAR_TYPE_FORMAT_RESULTS:
"""
Returns the uppercased clear type name, e.g. NORMAL CLEAR.
The integer will be converted to `ArcaeaPlayResultClearType` enum,
and will return "UNKNOWN" if the convertion fails.
Raises `ValueError` if the integer is negative.
"""
...
@overload
@classmethod
def clear_type(cls, clear_type: None) -> CLEAR_TYPE_FORMAT_RESULTS:
"""
Returns "None"
"""
...
@classmethod
def clear_type(cls, clear_type: Any) -> CLEAR_TYPE_FORMAT_RESULTS:
if clear_type is None:
return "None"
elif isinstance(clear_type, ArcaeaPlayResultClearType):
return clear_type.name.replace("_", " ").upper() # type: ignore
elif isinstance(clear_type, int):
if clear_type < 0:
raise ValueError("clear_type cannot be negative")
try:
return cls.clear_type(ArcaeaPlayResultClearType(clear_type))
except ValueError:
return "UNKNOWN"
else:
raise TypeError(f"Unsupported type {type(clear_type)}, cannot format")
MODIFIER_FORMAT_RESULTS = Literal["NORMAL", "EASY", "HARD", "UNKNOWN", "None"]
@overload
@classmethod
def modifier(cls, modifier: ArcaeaPlayResultModifier) -> MODIFIER_FORMAT_RESULTS:
"""
Returns the uppercased clear type name, e.g. NORMAL CLEAR.
"""
...
@overload
@classmethod
def modifier(cls, modifier: int) -> MODIFIER_FORMAT_RESULTS:
"""
Returns the uppercased clear type name, e.g. NORMAL CLEAR.
The integer will be converted to `ArcaeaPlayResultModifier` enum,
and will return "UNKNOWN" if the convertion fails.
Raises `ValueError` if the integer is negative.
"""
...
@overload
@classmethod
def modifier(cls, modifier: None) -> MODIFIER_FORMAT_RESULTS:
"""
Returns "None"
"""
...
@classmethod
def modifier(cls, modifier: Any) -> MODIFIER_FORMAT_RESULTS:
if modifier is None:
return "None"
elif isinstance(modifier, ArcaeaPlayResultModifier):
return modifier.name
elif isinstance(modifier, int):
if modifier < 0:
raise ValueError("modifier cannot be negative")
try:
return cls.modifier(ArcaeaPlayResultModifier(modifier))
except ValueError:
return "UNKNOWN"
else:
raise TypeError(f"Unsupported type {type(modifier)}, cannot format")

View File

@ -0,0 +1,83 @@
from typing import Any, Literal, overload
from arcaea_offline.constants.enums import ArcaeaRatingClass
class RatingClassFormatter:
abbreviations = {
ArcaeaRatingClass.PAST: "PST",
ArcaeaRatingClass.PRESENT: "PRS",
ArcaeaRatingClass.FUTURE: "FTR",
ArcaeaRatingClass.BEYOND: "BYD",
ArcaeaRatingClass.ETERNAL: "ETR",
}
NAME_FORMAT_RESULTS = Literal[
"Past", "Present", "Future", "Beyond", "Eternal", "Unknown"
]
@overload
@classmethod
def name(cls, rating_class: ArcaeaRatingClass) -> NAME_FORMAT_RESULTS:
"""
Returns the capitalized rating class name, e.g. Future.
"""
...
@overload
@classmethod
def name(cls, rating_class: int) -> NAME_FORMAT_RESULTS:
"""
Returns the capitalized rating class name, e.g. Future.
The integer will be converted to `ArcaeaRatingClass` enum,
and will return "Unknown" if the convertion fails.
"""
...
@classmethod
def name(cls, rating_class: Any) -> NAME_FORMAT_RESULTS:
if isinstance(rating_class, ArcaeaRatingClass):
return rating_class.name.lower().capitalize() # type: ignore
elif isinstance(rating_class, int):
try:
return cls.name(ArcaeaRatingClass(rating_class))
except ValueError:
return "Unknown"
else:
raise TypeError(f"Unsupported type: {type(rating_class)}, cannot format")
ABBREVIATION_FORMAT_RESULTS = Literal["PST", "PRS", "FTR", "BYD", "ETR", "UNK"]
@overload
@classmethod
def abbreviation(
cls, rating_class: ArcaeaRatingClass
) -> ABBREVIATION_FORMAT_RESULTS:
"""
Returns the uppercased rating class name, e.g. FTR.
"""
...
@overload
@classmethod
def abbreviation(cls, rating_class: int) -> ABBREVIATION_FORMAT_RESULTS:
"""
Returns the uppercased rating class name, e.g. FTR.
The integer will be converted to `ArcaeaRatingClass` enum,
and will return "UNK" if the convertion fails.
"""
...
@classmethod
def abbreviation(cls, rating_class: Any) -> ABBREVIATION_FORMAT_RESULTS:
if isinstance(rating_class, ArcaeaRatingClass):
return cls.abbreviations[rating_class] # type: ignore
elif isinstance(rating_class, int):
try:
return cls.abbreviation(ArcaeaRatingClass(rating_class))
except ValueError:
return "UNK"
else:
raise TypeError(f"Unsupported type: {type(rating_class)}, cannot format")

View File

@ -1,25 +0,0 @@
from typing import Optional
RATING_CLASS_TEXT_MAP = {
0: "Past",
1: "Present",
2: "Future",
3: "Beyond",
4: "Eternal",
}
RATING_CLASS_SHORT_TEXT_MAP = {
0: "PST",
1: "PRS",
2: "FTR",
3: "BYD",
4: "ETR",
}
def rating_class_to_text(rating_class: int) -> Optional[str]:
return RATING_CLASS_TEXT_MAP.get(rating_class)
def rating_class_to_short_text(rating_class: int) -> Optional[str]:
return RATING_CLASS_SHORT_TEXT_MAP.get(rating_class)

View File

@ -1,46 +0,0 @@
from typing import Any, Sequence
SCORE_GRADE_FLOOR = [9900000, 9800000, 9500000, 9200000, 8900000, 8600000, 0]
SCORE_GRADE_TEXTS = ["EX+", "EX", "AA", "A", "B", "C", "D"]
MODIFIER_TEXTS = ["NORMAL", "EASY", "HARD"]
CLEAR_TYPE_TEXTS = [
"TRACK LOST",
"NORMAL CLEAR",
"FULL RECALL",
"PURE MEMORY",
"EASY CLEAR",
"HARD CLEAR",
]
def zip_score_grade(score: int, __seq: Sequence, default: Any = "__PRESERVE__"):
"""
zip_score_grade is a simple wrapper that equals to:
```py
for score_floor, val in zip(SCORE_GRADE_FLOOR, __seq):
if score >= score_floor:
return val
return seq[-1] if default == "__PRESERVE__" else default
```
Could be useful in specific cases.
"""
return next(
(
val
for score_floor, val in zip(SCORE_GRADE_FLOOR, __seq)
if score >= score_floor
),
__seq[-1] if default == "__PRESERVE__" else default,
)
def score_to_grade_text(score: int) -> str:
return zip_score_grade(score, SCORE_GRADE_TEXTS)
def modifier_to_text(modifier: int) -> str:
return MODIFIER_TEXTS[modifier]
def clear_type_to_text(clear_type: int) -> str:
return CLEAR_TYPE_TEXTS[clear_type]

View File

@ -1,6 +0,0 @@
import json
from typing import List, Optional
def recover_search_title(db_value: Optional[str]) -> List[str]:
return json.loads(db_value) if db_value else []