Refactor entire script into a class
- Shove all the logic in a class - Allow for `cleanup_SECTION` functions, one that returns true will delete the calling section in post-run - Get rid of that awful awful `globals()` call, which was the main motivation of this - Document the new methods This should result in a horrifying diff that claims 98% of the file has changed but no actual change in logic or output.
This commit is contained in:
parent
eb924c14c2
commit
14ec718890
1 changed files with 332 additions and 287 deletions
137
main.py
137
main.py
|
@ -2,7 +2,7 @@
|
|||
|
||||
"""Parse BCEX (or BCCE) logs into json objects."""
|
||||
|
||||
__version__ = "0.4.0"
|
||||
__version__ = "0.4.1"
|
||||
__author__ = "Trysdyn Black"
|
||||
|
||||
import json
|
||||
|
@ -10,6 +10,16 @@ import sys
|
|||
from pathlib import Path
|
||||
|
||||
|
||||
class Parser:
|
||||
"""BCEX/BCCE spoiler logfile parser."""
|
||||
|
||||
def __init__(self, filename: str) -> None:
|
||||
"""Initialize parser with filename."""
|
||||
self.filename = filename
|
||||
self.config_sections = {}
|
||||
self.data_sections = {}
|
||||
|
||||
@staticmethod
|
||||
def parse_MONSTERS(data: str) -> dict[str, dict]: # noqa: C901, PLR0912
|
||||
"""
|
||||
Parse the MONSTERS section.
|
||||
|
@ -67,7 +77,7 @@ def parse_MONSTERS(data: str) -> dict[str, dict]: # noqa: C901, PLR0912
|
|||
|
||||
return result
|
||||
|
||||
|
||||
@staticmethod
|
||||
def parse_REMONSTERATE(data: str) -> dict[str, dict]:
|
||||
"""
|
||||
Parse the BCCE-only REMONSTERATE section.
|
||||
|
@ -86,7 +96,7 @@ def parse_REMONSTERATE(data: str) -> dict[str, dict]:
|
|||
|
||||
return result
|
||||
|
||||
|
||||
@staticmethod
|
||||
def parse_CHARACTERS(data: str) -> dict[str, dict]: # noqa: C901
|
||||
"""
|
||||
Parse the CHARACTERS section.
|
||||
|
@ -98,7 +108,11 @@ def parse_CHARACTERS(data: str) -> dict[str, dict]: # noqa: C901
|
|||
Regardless of flavor, core logic will snap stats back into this section
|
||||
later.
|
||||
"""
|
||||
replacements = {"Looks like": "looks", "World of Ruin location": "wor_location", "Notable equipment": "equipment"}
|
||||
replacements = {
|
||||
"Looks like": "looks",
|
||||
"World of Ruin location": "wor_location",
|
||||
"Notable equipment": "equipment",
|
||||
}
|
||||
|
||||
result = {}
|
||||
|
||||
|
@ -152,7 +166,7 @@ def parse_CHARACTERS(data: str) -> dict[str, dict]: # noqa: C901
|
|||
|
||||
return result
|
||||
|
||||
|
||||
@staticmethod
|
||||
def parse_STATS(data: str) -> dict[str, dict]:
|
||||
"""
|
||||
Parse the BCCE-only STATS section.
|
||||
|
@ -187,7 +201,7 @@ def parse_STATS(data: str) -> dict[str, dict]:
|
|||
|
||||
return result
|
||||
|
||||
|
||||
@staticmethod
|
||||
def parse_COMMANDS(data: str) -> dict[str, dict]:
|
||||
"""
|
||||
Parse the COMMANDS section.
|
||||
|
@ -227,7 +241,7 @@ def parse_COMMANDS(data: str) -> dict[str, dict]:
|
|||
|
||||
return commands
|
||||
|
||||
|
||||
@staticmethod
|
||||
def parse_SEED(data: str) -> dict[str, bool | str]:
|
||||
"""
|
||||
Parse the injected SEED section.
|
||||
|
@ -248,7 +262,7 @@ def parse_SEED(data: str) -> dict[str, bool | str]:
|
|||
"seed": data,
|
||||
}
|
||||
|
||||
|
||||
@staticmethod
|
||||
def parse_SECRET_ITEMS(data: str) -> list[str]:
|
||||
"""
|
||||
Parse the BCCE-only SECRET ITEMS section.
|
||||
|
@ -259,12 +273,51 @@ def parse_SECRET_ITEMS(data: str) -> list[str]:
|
|||
# I have no idea what this is lol, dump it to a list for now
|
||||
return [line for line in data.split("\n") if not line.startswith("---")]
|
||||
|
||||
@staticmethod
|
||||
def cleanup_STATS(data: dict) -> bool:
|
||||
"""
|
||||
Fold BCCE-only STATS section back into CHARACTERS data.
|
||||
|
||||
def load(filename: str) -> dict[str, str]:
|
||||
"""Load file and tokenize into sections."""
|
||||
# Load our file, tokenize by section header (starting with ====)
|
||||
with Path(filename).open(encoding="utf-8") as infile:
|
||||
tok_data = infile.read().split("============================================================\n")
|
||||
This returns BCCE logs back to how they were laid out in BCEX: where stat blocks
|
||||
were simply part of the CHARACTERS data.
|
||||
|
||||
The BCCE STATS section keys on character slot (Terra, Locke, etc) and not the
|
||||
new randomized character name, so some hunting has to happen here.
|
||||
"""
|
||||
for slot, stats in data.get("STATS", {}).items():
|
||||
for c_data in data.get("CHARACTERS", {}).values():
|
||||
if c_data["originally"].lower() == slot.lower():
|
||||
c_data["stats"] = stats
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def cleanup_COMMANDS(data: dict) -> bool:
|
||||
"""Fold COMMANDS expanded descriptions into CHARACTERS command data."""
|
||||
# If our COMMANDS section is missing or somehow missing a given command, we just
|
||||
# repeat the command's name as its description. This should only be simple things
|
||||
# like "fight" and "magic" unless something goes wrong.
|
||||
command_info = data.get("COMMANDS", {})
|
||||
for c_data in data.get("CHARACTERS", {}).values():
|
||||
for command in c_data.get("commands", {}):
|
||||
c_data["commands"][command] = command_info.get(command, command)
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def cleanup_REMONSTERATE(data: dict) -> bool:
|
||||
"""Fold REMONSTERATE section into MONSTERS section data."""
|
||||
for name, info in data.get("REMONSTERATE", {}).items():
|
||||
for m_name, m_info in data.get("MONSTERS", {}).items():
|
||||
if name == m_name:
|
||||
m_info["originally"] = info["originally"]
|
||||
m_info["sprite"] = info["sprite"]
|
||||
|
||||
return True
|
||||
|
||||
def get_sections(self, data: str) -> dict[str, str]:
|
||||
"""Split logfile text and return a dict of sections for parsing."""
|
||||
tok_data = data.split("============================================================\n")
|
||||
|
||||
sections = {}
|
||||
|
||||
|
@ -280,51 +333,43 @@ def load(filename: str) -> dict[str, str]:
|
|||
section_header, section_data = s.split("\n", 1)
|
||||
sections[section_header[5:]] = section_data
|
||||
|
||||
self.config_sections = sections
|
||||
return sections
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sections = load(sys.argv[1])
|
||||
def parse(self) -> dict:
|
||||
"""Fully parse the logfile and return the full data object."""
|
||||
# Get individual sections to work on
|
||||
with Path(self.filename).open(encoding="utf-8") as infile:
|
||||
sections = self.get_sections(infile.read())
|
||||
|
||||
data = {}
|
||||
|
||||
# This mess tries to run a function named parse_SECTION for each section,
|
||||
# and just continues to the next section if one doesn't exist.
|
||||
# For each section attempt to run a parser function for it
|
||||
for k, v in sections.items():
|
||||
try:
|
||||
section_func = f"parse_{k.replace(' ', '_')}"
|
||||
data[k] = globals()[section_func](v)
|
||||
except KeyError:
|
||||
continue
|
||||
if hasattr(self, section_func):
|
||||
data[k] = getattr(self, section_func)(v)
|
||||
|
||||
# Hydrate each character's command list with descriptions of the commands
|
||||
# This uses the COMMANDS section, but if one doesn't exist just repeat the command
|
||||
# name because it should be simple things like "fight" and "magic"
|
||||
command_info = data.get("COMMANDS", {})
|
||||
for c_data in data["CHARACTERS"].values():
|
||||
for command in c_data["commands"]:
|
||||
c_data["commands"][command] = command_info.get(command, command)
|
||||
# Do post-parse cleanup. We need all sections parsed to do these
|
||||
# Any cleanup function that returns true has its respective section deleted
|
||||
section_dels = set()
|
||||
for k in data:
|
||||
section_func = f"cleanup_{k.replace(' ', '_')}"
|
||||
if hasattr(self, section_func) and getattr(self, section_func)(data):
|
||||
section_dels.add(k)
|
||||
|
||||
# If we have a STATS block, snap it into CHARACTER data
|
||||
# BCCE broke this out into its own section
|
||||
# Worse, it keys on slot name, not randomized character name
|
||||
if "STATS" in data:
|
||||
for slot, stats in data["STATS"].items():
|
||||
for c_data in data["CHARACTERS"].values():
|
||||
if c_data["originally"].lower() == slot.lower():
|
||||
c_data["stats"] = stats
|
||||
# Any section cleanup that returns true means delete that section
|
||||
for k in section_dels:
|
||||
if k in data:
|
||||
del data[k]
|
||||
|
||||
del data["STATS"]
|
||||
self.data_sections = data
|
||||
return data
|
||||
|
||||
# If we ran BCCE Remonsterate, fold sprite data into monster block
|
||||
if "REMONSTERATE" in data:
|
||||
for name, info in data["REMONSTERATE"].items():
|
||||
for m_name, m_info in data["MONSTERS"].items():
|
||||
if name == m_name:
|
||||
m_info["originally"] = info["originally"]
|
||||
m_info["sprite"] = info["sprite"]
|
||||
|
||||
del data["REMONSTERATE"]
|
||||
if __name__ == "__main__":
|
||||
p = Parser(sys.argv[1])
|
||||
data = p.parse()
|
||||
|
||||
# Barf this pile of trash out
|
||||
print(json.dumps(data))
|
||||
|
|
Loading…
Add table
Reference in a new issue