Files
ttrpg_manager/scripts/parse_mpmb.py
T
NilsBriggen 1a9e5e2c18 Build MVP: campaigns, characters, combat, dice, compendium
- Rules abstraction (5e + pf2e) behind one interface, fully tested
- Pure combat engine: turn-order safe across add/remove/reorder/init-change
- Dexie storage with real cascade deletes + Zod validation on write
- Seedable dice engine with notation parser
- Lazy SRD compendium (code-split), bestiary -> combat
- Strict TS, 54 unit tests, Playwright e2e smoke (all green)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 00:09:42 +02:00

124 lines
4.5 KiB
Python

#!/usr/bin/env python3
"""Parse MPMB all_WotC_published.js into structured JSON files"""
import re, json, os
INPUT = "/home/nilsb/Downloads/all_WotC_published.js"
OUT_DIR = "/home/nilsb/Documents/Projects/TTRPG_manager/src/assets/srd"
def parse_mpmb(filepath):
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# Find all list assignments: ListName["key"] = { ... };
# Strategy: extract each list type by finding assignment patterns
lists = {}
list_names = [
"SourceList", "MagicItemsList", "SpellsList", "ClassList",
"RaceList", "FeatsList", "BackgroundList", "BackgroundFeatureList",
"CreatureList", "WeaponsList", "AmmoList", "GearList", "CompanionList"
]
for list_name in list_names:
entries = {}
# Pattern: ListName["key"] = { ... };
# Match multi-line objects by counting braces
pattern = re.escape(list_name) + r'\["([^"]+)"\]\s*=\s*\{'
for match in re.finditer(pattern, content):
key = match.group(1)
start = match.end() - 1 # position of opening {
# Find matching closing brace
depth = 0
i = start
in_string = False
string_char = None
while i < len(content):
c = content[i]
if in_string:
if c == '\\':
i += 2
continue
if c == string_char:
in_string = False
i += 1
continue
if c in ('"', "'"):
in_string = True
string_char = c
elif c == '{':
depth += 1
elif c == '}':
depth -= 1
if depth == 0:
obj_str = content[start:i+1]
break
elif c == '/' and i+1 < len(content) and content[i+1] in ('/', '*'):
# Skip comments
if content[i+1] == '/':
while i < len(content) and content[i] != '\n':
i += 1
continue
elif content[i+1] == '*':
i += 2
while i < len(content) and not (content[i] == '*' and content[i+1] == '/'):
i += 1
i += 2
continue
i += 1
else:
continue # no closing brace found
# Store raw object text (can't safely eval, but we can extract key fields)
entries[key] = obj_str
if entries:
lists[list_name] = entries
print(f" {list_name}: {len(entries)} entries")
return lists
def safe_extract_fields(obj_str):
"""Extract common fields from a JS object literal string."""
fields = {}
patterns = {
'name': r'name\s*:\s*["\']([^"\']+)["\']',
'type': r'type\s*:\s*["\']([^"\']+)["\']',
'rarity': r'rarity\s*:\s*["\']([^"\']+)["\']',
'source': r'source\s*:\s*(\[\[.*?\]\])',
'description': r'description\s*:\s*["\']([^"]+)["\']',
'weight': r'weight\s*:\s*(-?\d+)',
'prerequisite': r'prerequisite\s*:\s*["\']([^"\']+)["\']',
}
for field, pattern in patterns.items():
m = re.search(pattern, obj_str)
if m:
val = m.group(1)
if field == 'weight':
val = int(val)
elif field == 'source':
# Parse [[SourceName, page], [SourceName2, page2]]
sources = re.findall(r'\["([^"]+)",?\s*(\d+)?\]', val)
val = [{"source": s[0], "page": int(s[1]) if s[1] else None} for s in sources]
fields[field] = val
return fields
if __name__ == "__main__":
print("Parsing MPMB data file...")
lists = parse_mpmb(INPUT)
os.makedirs(OUT_DIR, exist_ok=True)
# Save raw entry counts
for name, entries in lists.items():
# Extract key fields for each entry
extracted = {}
for key, obj_str in entries.items():
extracted[key] = safe_extract_fields(obj_str)
outname = f"mpmb-{name.lower().replace('list','')}.json"
outpath = os.path.join(OUT_DIR, outname)
with open(outpath, 'w') as f:
json.dump(extracted, f, indent=2)
print(f" Saved {outname}: {len(extracted)} entries")
print("\nDone! Files saved to", OUT_DIR)