-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactoring: splitted mtf2json.py into multiple modules
- Loading branch information
Showing
11 changed files
with
869 additions
and
805 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
# this enables direct import from 'mtf2json' (instead of 'mtf2json.mtf2json') | ||
from .mtf2json import read_mtf, write_json, ConversionError, version, mm_commit # noqa | ||
from .mtf2json import read_mtf, write_json, version, mm_commit # noqa | ||
from .error import ConversionError # noqa |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
from typing import Any | ||
|
||
|
||
def add_armor_type(value: str, mech_data: dict[str, Any]) -> None: | ||
""" | ||
Add the armor type. | ||
The MTF `Armor:` key in is a bit of a mess: it can contain a value that only describes | ||
the type of armor, e.g: | ||
``` | ||
Armor:Standard Armor | ||
``` | ||
or the type + tech base (delimited by `(...)`), e.g.: | ||
``` | ||
Armor:Standard(Inner Sphere) | ||
``` | ||
Furthermore the type description is not consistent. E.g. it can be `Standard` or | ||
`Standard Armor`. We try to clean up the mess a bit by storing all available | ||
information in the JSON 'armor' section, while also choosing a consistent type string: | ||
``` | ||
"armor": { | ||
"type": "Standard", | ||
"tech_base: "Inner Sphere" | ||
... | ||
} | ||
``` | ||
Note that `tech_base` is optional (only added if there's a string in `(...)`) and | ||
that the term 'Armor' has been removed from the type string. | ||
""" | ||
# create armor section if it doesn't exist | ||
if "armor" not in mech_data: | ||
mech_data["armor"] = {} | ||
armor_type_section: dict[str, str | dict[str, Any]] = mech_data["armor"] | ||
|
||
# Extract type and tech base if present | ||
if "(" in value and ")" in value: | ||
type_, tech_base = value.split("(", 1) | ||
tech_base = tech_base.rstrip(")") | ||
else: | ||
type_ = value | ||
tech_base = None | ||
|
||
# Clean up type string | ||
type_ = type_.replace(" Armor", "").strip() | ||
|
||
# Populate armor section | ||
armor_type_section["type"] = type_ | ||
if tech_base: | ||
armor_type_section["tech_base"] = tech_base.strip() | ||
|
||
|
||
def add_armor_locations(key: str, value: str, mech_data: dict[str, Any]) -> None: | ||
""" | ||
Add individual armor locations to the given `armor_section` dictionary. | ||
The armor pips are stored as individual keys in an MTF file: | ||
``` | ||
LA armor:21 | ||
RA armor:21 | ||
LT armor:30 | ||
RT armor:30 | ||
CT armor:40 | ||
HD armor:9 | ||
LL armor:26 | ||
RL armor:26 | ||
RTL armor:10 | ||
RTR armor:10 | ||
RTC armor:17 | ||
``` | ||
We're structuring the values in the JSON 'armor' section like this: | ||
``` | ||
"armor": { | ||
... | ||
"left_arm": { | ||
"pips": 21 | ||
}, | ||
"right_arm": { | ||
"pips": 21 | ||
}, | ||
"left_torso": { | ||
"front": { | ||
"pips": 30 | ||
}, | ||
"rear": { | ||
"pips": 10 | ||
}, | ||
}, | ||
"right_torso": { | ||
"front": { | ||
"pips": 30 | ||
}, | ||
"rear": { | ||
"pips": 10 | ||
}, | ||
}, | ||
"center_torso": { | ||
"front": { | ||
"pips": 40 | ||
}, | ||
"rear": { | ||
"pips": 17 | ||
}, | ||
}, | ||
"head": { | ||
"pips": 9 | ||
} | ||
"left_leg": { | ||
"pips": 26 | ||
}, | ||
"right_leg": { | ||
"pips": 26 | ||
} | ||
} | ||
``` | ||
In case of patchwork armor, the location keys MAY contain subkeys | ||
describing the armor type in that location, e.g.: | ||
``` | ||
HD Armor:Standard(IS/Clan):9 | ||
LL Armor:Reactive(Inner Sphere):34 | ||
RTL Armor:8 | ||
``` | ||
In that case, we add a `type` key to the affected location: | ||
``` | ||
"head": { | ||
"pips": 9, | ||
"type": "Standard(IS/Clan)" | ||
} | ||
"left_leg": { | ||
"pips": 26, | ||
"type": "Reactive(Inner Sphere)" | ||
}, | ||
``` | ||
""" | ||
# create armor section if it doesn't exist | ||
if "armor" not in mech_data: | ||
mech_data["armor"] = {} | ||
armor_section: dict[str, Any] = mech_data["armor"] | ||
|
||
# Extract subkeys if present | ||
parts = value.split(":") | ||
if len(parts) == 2: | ||
armor_type = parts[0].strip() | ||
pips_value = int(parts[1].strip()) | ||
else: | ||
armor_type = None | ||
pips_value = int(parts[0].strip()) | ||
|
||
# center torso (front and rear) | ||
if key in ["ct_armor", "rtc_armor"]: | ||
if "center_torso" not in armor_section: | ||
armor_section["center_torso"] = {} | ||
side = "front" if key == "ct_armor" else "rear" | ||
if side not in armor_section["center_torso"]: | ||
armor_section["center_torso"][side] = {} | ||
armor_section["center_torso"][side]["pips"] = pips_value | ||
if armor_type: | ||
armor_section["center_torso"][side]["type"] = armor_type | ||
# right torso (front and rear) | ||
elif key in ["rt_armor", "rtr_armor"]: | ||
if "right_torso" not in armor_section: | ||
armor_section["right_torso"] = {} | ||
side = "front" if key == "rt_armor" else "rear" | ||
if side not in armor_section["right_torso"]: | ||
armor_section["right_torso"][side] = {} | ||
armor_section["right_torso"][side]["pips"] = pips_value | ||
if armor_type: | ||
armor_section["right_torso"][side]["type"] = armor_type | ||
# left torso (front and rear) | ||
elif key in ["lt_armor", "rtl_armor"]: | ||
if "left_torso" not in armor_section: | ||
armor_section["left_torso"] = {} | ||
side = "front" if key == "lt_armor" else "rear" | ||
if side not in armor_section["left_torso"]: | ||
armor_section["left_torso"][side] = {} | ||
armor_section["left_torso"][side]["pips"] = pips_value | ||
if armor_type: | ||
armor_section["left_torso"][side]["type"] = armor_type | ||
else: | ||
if key not in armor_section: | ||
armor_section[key] = {} | ||
armor_section[key]["pips"] = pips_value | ||
if armor_type: | ||
armor_section[key]["type"] = armor_type |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
from typing import Any | ||
|
||
|
||
def add_crit_slot(key: str, value: str, mech_data: dict[str, Any]) -> None: | ||
""" | ||
Add a critical slot entry. | ||
The MTF contains one critical slot section per location. It start with the location | ||
name and no value. Each following line contains a slot entry with no key. However, | ||
it may contain a `:` if it contains a `:SIZE:` option, but that is filtered by | ||
`__read_line()`. | ||
Here's an example for the left arm: | ||
``` | ||
Left Arm: | ||
Shoulder | ||
Upper Arm Actuator | ||
Lower Arm Actuator | ||
Hand Actuator | ||
Heat Sink | ||
Heat Sink | ||
ISERLargeLaser | ||
ISERLargeLaser | ||
ISAntiMissileSystem | ||
-Empty- | ||
-Empty- | ||
-Empty- | ||
``` | ||
In JSON, we put all locations into the `critical_slots` section and add a counter for each slot. | ||
Also, we replace `-Empty-` with `None`: | ||
``` | ||
"critical_slots": { | ||
"left_arm": { | ||
"1": "Shoulder", | ||
"2": "Upper Arm Actuator", | ||
"3": "Lower Arm Actuator", | ||
"4": "Hand Actuator", | ||
"5": "Heat Sink", | ||
"6": "Heat Sink", | ||
"7": "ISERLargeLaser", | ||
"8": "ISERLargeLaser", | ||
"9": "ISAntiMissileSystem", | ||
"10": None, | ||
"11": None, | ||
"12": None | ||
}, | ||
``` | ||
""" | ||
# create section if it does not exist | ||
if "critical_slots" not in mech_data: | ||
mech_data["critical_slots"] = {} | ||
|
||
# create new subsection if value is empty | ||
if not value: | ||
mech_data["critical_slots"][key] = {} | ||
# otherwise add the given slot entry | ||
else: | ||
crit_slots_section: dict[str, str | None] = mech_data["critical_slots"][key] | ||
slot_number = len(crit_slots_section) + 1 | ||
crit_slots_section[str(slot_number)] = value if value != "-Empty-" else None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
class ConversionError(Exception): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
from typing import Any, cast | ||
from .error import ConversionError | ||
|
||
|
||
def add_fluff(key: str, value: str, mech_data: dict[str, Any]) -> None: | ||
value = __remove_p_tags(value) | ||
""" | ||
Add the given fluff key and value. | ||
Some of the fluff keys can appear multiple times in an MTF file, e.g.: | ||
``` | ||
systemmanufacturer:CHASSIS:Star League | ||
systemmanufacturer:ENGINE:GM | ||
systemmanufacturer:ARMOR:Starshield | ||
systemmanufacturer:TARGETING:Dalban | ||
systemmanufacturer:COMMUNICATIONS:Dalban | ||
systemmode:ENGINE:380 | ||
systemmode:CHASSIS:XT | ||
systemmode:TARGETING:HiRez-B | ||
systemmode:COMMUNICATIONS:Commline | ||
``` | ||
All fluff keys that appears more than once have "subkeys", i.e. the given | ||
value contains another key / value pair separated by `:`. We create a JSON | ||
subsection for each "primary" key and add the "subkey" entries to it, e.g.: | ||
``` | ||
"systemmanufacturer": { | ||
"chassis": "Star League", | ||
"engine": "GM", | ||
"armor": "Starshield" | ||
"targeting": "Dalban", | ||
"communication": "Dalban" | ||
}, | ||
"systemmode": { | ||
"engine": "380", | ||
"chassis": "XT", | ||
"targeting": "HiRez-B", | ||
"communications": "Commline" | ||
}, | ||
... | ||
The following keys can contain lists as values (separated by `,`): | ||
``` | ||
manufacturer | ||
primaryfactory | ||
``` | ||
In this case we store the values as JSON lists, e.g.: | ||
``` | ||
"manufacturer": [ | ||
"Defiance Industries", | ||
"Hegemony Research and Development Department", | ||
"Weapons Division" | ||
], | ||
"primaryfactory": [ | ||
"Hesperus II", | ||
"New Earth" | ||
], | ||
... | ||
``` | ||
All other keys and values are added verbatim. | ||
""" | ||
# add fluff section if not present | ||
if "fluff" not in mech_data: | ||
mech_data["fluff"] = {} | ||
fluff_section: dict[str, str | list[str] | dict[str, str]] = mech_data["fluff"] | ||
|
||
# the key is already in the fluff section | ||
# -> it's a subsection | ||
if key in fluff_section: | ||
try: | ||
subkey, subvalue = value.split(":", 1) | ||
except ValueError: | ||
raise ConversionError( | ||
f"Key '{key}' already exists in the fluff section but value is missing the ':' delimiter!" | ||
) | ||
if isinstance(fluff_section[key], dict): | ||
cast(dict, fluff_section[key])[subkey.lower()] = subvalue.strip() | ||
else: | ||
raise ConversionError( | ||
f"Tried to add '{subkey}:{subvalue}' to fluff section '{key}', but '{key}' is not a dictionary!" | ||
) | ||
# the key is new | ||
else: | ||
# value contains a subkey | ||
# -> create a new subsection | ||
if ":" in value: | ||
subkey, subvalue = value.split(":", 1) | ||
# but ONLY if the subkey is all UPPERCASE, e.g.: | ||
# ``` | ||
# systemmanufacturer:CHASSIS:Republic-R | ||
# ``` | ||
# Otherwise we could turn some of the longer text strings | ||
# (that sometimes contain `:`) into dicts. | ||
if subkey.isupper(): | ||
fluff_section[key] = {subkey.lower(): subvalue.strip()} | ||
else: | ||
fluff_section[key] = value | ||
# value contains a list | ||
elif key in ["manufacturer", "primaryfactory"]: | ||
fluff_section[key] = [item.strip() for item in value.split(",")] | ||
# simple value | ||
else: | ||
fluff_section[key] = value | ||
|
||
|
||
def __remove_p_tags(text: str) -> str: | ||
""" | ||
Remove <p>, <P>, </p> and </P> tags from the given text. | ||
""" | ||
return ( | ||
text.replace("<p>", "") | ||
.replace("</p>", "") | ||
.replace("<P>", "") | ||
.replace("</P>", "") | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from typing import Any | ||
|
||
|
||
def add_heat_sinks(value: str, mech_data: dict[str, Any]) -> None: | ||
""" | ||
Add heat sinks section. | ||
Heat sinks are stored as a flat key:value pair in the MTF file, with the value containing | ||
both type and quantity of heat sinks, e.g.: | ||
``` | ||
heat sinks:10 IS Double | ||
``` | ||
We separate heat sink type and quantity, using the first ` ` as delimiter, and store them | ||
in a JSON section like this: | ||
``` | ||
"heat_sinks": { | ||
"quantity": 10, | ||
"type": "IS Double" | ||
} | ||
``` | ||
""" | ||
# add heat sinks section if not present | ||
if "heat_sinks" not in mech_data: | ||
mech_data["heat_sinks"] = {} | ||
heat_sinks_section: dict[str, int | str] = mech_data["heat_sinks"] | ||
|
||
quantity, type_ = value.split(" ", 1) | ||
heat_sinks_section["quantity"] = int(quantity) | ||
heat_sinks_section["type"] = type_.strip() |
Oops, something went wrong.