From 6a486c4f36e269b5eb1dc640142a8eff5b40d6b6 Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Tue, 29 Sep 2020 11:58:56 -0400 Subject: [PATCH] Initial Commit: working json -> hierarchical yaml unpacking --- .gitignore | 2 + poetry.lock | 42 +++++++++++++++++ pyproject.toml | 19 ++++++++ tts_yaml_unpacker.py | 107 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 170 insertions(+) create mode 100644 .gitignore create mode 100644 poetry.lock create mode 100644 pyproject.toml create mode 100755 tts_yaml_unpacker.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aadc86c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.egg-info/ +__pycache__/ diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..0b87bf9 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,42 @@ +[[package]] +name = "pathvalidate" +version = "2.3.0" +description = "pathvalidate is a Python library to sanitize/validate a string such as filenames/file-paths/etc." +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +test = ["allpairspy", "click", "faker", "pytest"] + +[[package]] +name = "pyyaml" +version = "5.3.1" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[metadata] +lock-version = "1.0" +python-versions = "^3.8" +content-hash = "9174acecc4f28cb786e63beb6c608fe788fb186e6485c64749543309a2b53121" + +[metadata.files] +pathvalidate = [ + {file = "pathvalidate-2.3.0-py3-none-any.whl", hash = "sha256:1697c8ea71ff4c48e7aa0eda72fe4581404be8f41e51a17363ef682dd6824d35"}, + {file = "pathvalidate-2.3.0.tar.gz", hash = "sha256:32d30dbacb711c16bb188b12ce7e9a46b41785f50a12f64500f747480a4b6ee3"}, +] +pyyaml = [ + {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, + {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, + {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"}, + {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"}, + {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"}, + {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"}, + {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"}, + {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"}, + {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"}, + {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, + {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..53c7bfb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "tts_yaml_unpacker" +version = "0.1.0" +description = "" +authors = ["Adam Goldsmith "] + +[tool.poetry.dependencies] +python = "^3.8" +pathvalidate = "^2.3.0" +pyyaml = "^5.3.1" + +[tool.poetry.dev-dependencies] + +[tool.poetry.scripts] +tts_yaml_unpacker = 'tts_yaml_unpacker:main' + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/tts_yaml_unpacker.py b/tts_yaml_unpacker.py new file mode 100755 index 0000000..74d26d7 --- /dev/null +++ b/tts_yaml_unpacker.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 + +import json +import yaml +import os +import sys +from pathvalidate import sanitize_filename + + +class IncludeTag: + yaml_tag = '!include' + + def __init__(self, target): + self.target = target + + @classmethod + def to_yaml(cls, dumper, node): + return dumper.represent_scalar(cls.yaml_tag, node.target) + + @classmethod + def from_yaml(cls, loader, node): + base_dir = os.path.dirname(loader.stream.name) + target = os.path.join(base_dir, node.value) + with open(target) as f: + if target.endswith('.yaml'): + return yaml.load(f, Loader=yaml.Loader) + else: + return f.read() + + +yaml.add_constructor('!include', IncludeTag.from_yaml) +yaml.add_representer(IncludeTag, IncludeTag.to_yaml) + + +def uniqueName(obj): + if 'SaveName' in obj: + return obj['SaveName'] + return f"{obj['Name']} {obj['Nickname']} {obj['GUID']}" + + +def shouldRecurse(obj, subobject_prop): + return subobject_prop in obj \ + and obj.get('Name') not in ('Deck', 'DeckCustom') \ + and len(obj[subobject_prop]) > 1 + + +def recursivelyUnpackObject(parent_dir, obj, + subobject_prop='ContainedObjects', base_name=None): + def convertObjectsToIncludes(objects): + for subObj in objects: + filename = recursivelyUnpackObject(file_base_path, subObj) + yield IncludeTag(os.path.join(obj_base_name, filename)) + + obj_base_name = base_name or sanitize_filename(uniqueName(obj)) + file_base_path = os.path.join(parent_dir, obj_base_name) + + if shouldRecurse(obj, subobject_prop): + os.makedirs(file_base_path, exist_ok=True) + + obj[subobject_prop] = list( + convertObjectsToIncludes(obj[subobject_prop])) + + if 'LuaScript' in obj and len(obj['LuaScript']) > 0: + with open(file_base_path + '.ttslua', 'w') as f: + f.write(obj['LuaScript']) + + obj['LuaScript'] = IncludeTag(obj_base_name + '.ttslua') + + with open(file_base_path + '.yaml', 'w') as f: + yaml.dump(obj, f) + + return obj_base_name + '.yaml' + + +def unpackJson(json_file, output_name): + with open(json_file) as f: + data = json.load(f) + + recursivelyUnpackObject( + '', data, subobject_prop='ObjectStates', base_name=output_name) + + +def packYaml(yaml_file, output_json_file): + with open(yaml_file) as f: + data = yaml.load(f, Loader=yaml.Loader) + + with open(output_json_file, 'w') as f: + json.dump(data, f, indent=2) + + +def usage(): + print(f"Usage: {sys.argv[0]} ") + + +def main(): + if len(sys.argv) != 4: + usage() + elif sys.argv[1] == 'unpack': + unpackJson(sys.argv[2], sys.argv[3]) + elif sys.argv[1] == 'pack': + packYaml(sys.argv[2], sys.argv[3]) + else: + usage() + + +if __name__ == '__main__': + main()