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."""
|
"""Parse BCEX (or BCCE) logs into json objects."""
|
||||||
|
|
||||||
__version__ = "0.4.0"
|
__version__ = "0.4.1"
|
||||||
__author__ = "Trysdyn Black"
|
__author__ = "Trysdyn Black"
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
@ -10,6 +10,16 @@ import sys
|
||||||
from pathlib import Path
|
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
|
def parse_MONSTERS(data: str) -> dict[str, dict]: # noqa: C901, PLR0912
|
||||||
"""
|
"""
|
||||||
Parse the MONSTERS section.
|
Parse the MONSTERS section.
|
||||||
|
@ -67,7 +77,7 @@ def parse_MONSTERS(data: str) -> dict[str, dict]: # noqa: C901, PLR0912
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def parse_REMONSTERATE(data: str) -> dict[str, dict]:
|
def parse_REMONSTERATE(data: str) -> dict[str, dict]:
|
||||||
"""
|
"""
|
||||||
Parse the BCCE-only REMONSTERATE section.
|
Parse the BCCE-only REMONSTERATE section.
|
||||||
|
@ -86,7 +96,7 @@ def parse_REMONSTERATE(data: str) -> dict[str, dict]:
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def parse_CHARACTERS(data: str) -> dict[str, dict]: # noqa: C901
|
def parse_CHARACTERS(data: str) -> dict[str, dict]: # noqa: C901
|
||||||
"""
|
"""
|
||||||
Parse the CHARACTERS section.
|
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
|
Regardless of flavor, core logic will snap stats back into this section
|
||||||
later.
|
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 = {}
|
result = {}
|
||||||
|
|
||||||
|
@ -152,7 +166,7 @@ def parse_CHARACTERS(data: str) -> dict[str, dict]: # noqa: C901
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def parse_STATS(data: str) -> dict[str, dict]:
|
def parse_STATS(data: str) -> dict[str, dict]:
|
||||||
"""
|
"""
|
||||||
Parse the BCCE-only STATS section.
|
Parse the BCCE-only STATS section.
|
||||||
|
@ -187,7 +201,7 @@ def parse_STATS(data: str) -> dict[str, dict]:
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def parse_COMMANDS(data: str) -> dict[str, dict]:
|
def parse_COMMANDS(data: str) -> dict[str, dict]:
|
||||||
"""
|
"""
|
||||||
Parse the COMMANDS section.
|
Parse the COMMANDS section.
|
||||||
|
@ -227,7 +241,7 @@ def parse_COMMANDS(data: str) -> dict[str, dict]:
|
||||||
|
|
||||||
return commands
|
return commands
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def parse_SEED(data: str) -> dict[str, bool | str]:
|
def parse_SEED(data: str) -> dict[str, bool | str]:
|
||||||
"""
|
"""
|
||||||
Parse the injected SEED section.
|
Parse the injected SEED section.
|
||||||
|
@ -248,7 +262,7 @@ def parse_SEED(data: str) -> dict[str, bool | str]:
|
||||||
"seed": data,
|
"seed": data,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def parse_SECRET_ITEMS(data: str) -> list[str]:
|
def parse_SECRET_ITEMS(data: str) -> list[str]:
|
||||||
"""
|
"""
|
||||||
Parse the BCCE-only SECRET ITEMS section.
|
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
|
# 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("---")]
|
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]:
|
This returns BCCE logs back to how they were laid out in BCEX: where stat blocks
|
||||||
"""Load file and tokenize into sections."""
|
were simply part of the CHARACTERS data.
|
||||||
# Load our file, tokenize by section header (starting with ====)
|
|
||||||
with Path(filename).open(encoding="utf-8") as infile:
|
The BCCE STATS section keys on character slot (Terra, Locke, etc) and not the
|
||||||
tok_data = infile.read().split("============================================================\n")
|
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 = {}
|
sections = {}
|
||||||
|
|
||||||
|
@ -280,51 +333,43 @@ def load(filename: str) -> dict[str, str]:
|
||||||
section_header, section_data = s.split("\n", 1)
|
section_header, section_data = s.split("\n", 1)
|
||||||
sections[section_header[5:]] = section_data
|
sections[section_header[5:]] = section_data
|
||||||
|
|
||||||
|
self.config_sections = sections
|
||||||
return sections
|
return sections
|
||||||
|
|
||||||
|
def parse(self) -> dict:
|
||||||
if __name__ == "__main__":
|
"""Fully parse the logfile and return the full data object."""
|
||||||
sections = load(sys.argv[1])
|
# Get individual sections to work on
|
||||||
|
with Path(self.filename).open(encoding="utf-8") as infile:
|
||||||
|
sections = self.get_sections(infile.read())
|
||||||
|
|
||||||
data = {}
|
data = {}
|
||||||
|
|
||||||
# This mess tries to run a function named parse_SECTION for each section,
|
# For each section attempt to run a parser function for it
|
||||||
# and just continues to the next section if one doesn't exist.
|
|
||||||
for k, v in sections.items():
|
for k, v in sections.items():
|
||||||
try:
|
|
||||||
section_func = f"parse_{k.replace(' ', '_')}"
|
section_func = f"parse_{k.replace(' ', '_')}"
|
||||||
data[k] = globals()[section_func](v)
|
if hasattr(self, section_func):
|
||||||
except KeyError:
|
data[k] = getattr(self, section_func)(v)
|
||||||
continue
|
|
||||||
|
|
||||||
# Hydrate each character's command list with descriptions of the commands
|
# Do post-parse cleanup. We need all sections parsed to do these
|
||||||
# This uses the COMMANDS section, but if one doesn't exist just repeat the command
|
# Any cleanup function that returns true has its respective section deleted
|
||||||
# name because it should be simple things like "fight" and "magic"
|
section_dels = set()
|
||||||
command_info = data.get("COMMANDS", {})
|
for k in data:
|
||||||
for c_data in data["CHARACTERS"].values():
|
section_func = f"cleanup_{k.replace(' ', '_')}"
|
||||||
for command in c_data["commands"]:
|
if hasattr(self, section_func) and getattr(self, section_func)(data):
|
||||||
c_data["commands"][command] = command_info.get(command, command)
|
section_dels.add(k)
|
||||||
|
|
||||||
# If we have a STATS block, snap it into CHARACTER data
|
# Any section cleanup that returns true means delete that section
|
||||||
# BCCE broke this out into its own section
|
for k in section_dels:
|
||||||
# Worse, it keys on slot name, not randomized character name
|
if k in data:
|
||||||
if "STATS" in data:
|
del data[k]
|
||||||
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
|
|
||||||
|
|
||||||
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
|
# Barf this pile of trash out
|
||||||
print(json.dumps(data))
|
print(json.dumps(data))
|
||||||
|
|
Loading…
Add table
Reference in a new issue