refactor: use JSON5 instead of JSON for BME prototype.

- use JSON5 for BME prototype description file instead of JSON to make us have ability that make comment in declaration files (TBD in future).
- upgrade corresponding scripts.
- confirm the finish of upgrading script into modern Python.
This commit is contained in:
2025-07-29 21:14:02 +08:00
parent ab266a07fb
commit 9e65d258d7
19 changed files with 29 additions and 26 deletions

View File

@ -2,12 +2,13 @@ import json, logging
from pathlib import Path from pathlib import Path
import common import common
from common import AssetKind from common import AssetKind
import json5
def _compress_json(src_file: Path, dst_file: Path) -> None: def _compress_json(src_file: Path, dst_file: Path) -> None:
# load data first # load data first
with open(src_file, 'r', encoding='utf-8') as f: with open(src_file, 'r', encoding='utf-8') as f:
loaded_prototypes = json.load(f) loaded_prototypes = json5.load(f)
# save result with compress config # save result with compress config
with open(dst_file, 'w', encoding='utf-8') as f: with open(dst_file, 'w', encoding='utf-8') as f:
@ -24,13 +25,14 @@ def build_jsons() -> None:
raw_jsons_dir = common.get_raw_assets_folder(AssetKind.Jsons) raw_jsons_dir = common.get_raw_assets_folder(AssetKind.Jsons)
plg_jsons_dir = common.get_plugin_assets_folder(AssetKind.Jsons) plg_jsons_dir = common.get_plugin_assets_folder(AssetKind.Jsons)
for raw_json_file in raw_jsons_dir.glob('*.json'): for raw_json_file in raw_jsons_dir.glob('*.json5'):
# Skip non-file. # Skip non-file.
if not raw_json_file.is_file(): if not raw_json_file.is_file():
continue continue
# Build final path # Build final path
plg_json_file = plg_jsons_dir / raw_json_file.relative_to(raw_jsons_dir) plg_json_file = plg_jsons_dir / raw_json_file.relative_to(raw_jsons_dir)
plg_json_file = plg_json_file.with_suffix('.json')
# Show message # Show message
logging.info(f'Compressing {raw_json_file} -> {plg_json_file}') logging.info(f'Compressing {raw_json_file} -> {plg_json_file}')

View File

@ -1,4 +1,4 @@
import os, typing, logging, enum import logging, enum, typing
from pathlib import Path from pathlib import Path

View File

@ -1,8 +1,8 @@
import json, logging, typing, itertools import logging, typing, itertools
from pathlib import Path from pathlib import Path
import common, bme import common, bme
from common import AssetKind from common import AssetKind
import pydantic, polib import pydantic, polib, json5
## YYC MARK: ## YYC MARK:
# This translation context string prefix is cpoied from UTIL_translation.py. # This translation context string prefix is cpoied from UTIL_translation.py.
@ -38,14 +38,14 @@ def _extract_json(json_file: Path) -> typing.Iterator[polib.POEntry]:
try: try:
# Read file and convert it into BME struct. # Read file and convert it into BME struct.
with open(json_file, 'r', encoding='utf-8') as f: with open(json_file, 'r', encoding='utf-8') as f:
document = json.load(f) document = json5.load(f)
prototypes = bme.Prototypes.model_validate(document) prototypes = bme.Prototypes.model_validate(document)
# Extract translation # Extract translation
return itertools.chain.from_iterable(_extract_prototype(prototype) for prototype in prototypes.root) return itertools.chain.from_iterable(_extract_prototype(prototype) for prototype in prototypes.root)
except json.JSONDecodeError:
logging.error(f'Can not extract translation from {json_file} due to JSON error. Please validate it first.')
except pydantic.ValidationError: except pydantic.ValidationError:
logging.error(f'Can not extract translation from {json_file} due to struct error. Please validate it first.') logging.error(f'Can not extract translation from {json_file} due to struct error. Please validate it first.')
except (ValueError, UnicodeDecodeError):
logging.error(f'Can not extract translation from {json_file} due to JSON5 error. Please validate it first.')
# Output nothing # Output nothing
return itertools.chain.from_iterable(()) return itertools.chain.from_iterable(())
@ -70,7 +70,7 @@ def extract_jsons() -> None:
} }
# Iterate all prototypes and add into POT # Iterate all prototypes and add into POT
for raw_json_file in raw_jsons_dir.glob('*.json'): for raw_json_file in raw_jsons_dir.glob('*.json5'):
# Skip non-file. # Skip non-file.
if not raw_json_file.is_file(): if not raw_json_file.is_file():
continue continue

View File

@ -3,6 +3,7 @@ name = "scripts"
version = "1.0.0" version = "1.0.0"
requires-python = ">=3.11" requires-python = ">=3.11"
dependencies = [ dependencies = [
"json5>=0.12.0",
"pillow==10.2.0", "pillow==10.2.0",
"polib>=1.2.0", "polib>=1.2.0",
"pydantic>=2.11.7", "pydantic>=2.11.7",

11
scripts/uv.lock generated
View File

@ -11,6 +11,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
] ]
[[package]]
name = "json5"
version = "0.12.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/12/be/c6c745ec4c4539b25a278b70e29793f10382947df0d9efba2fa09120895d/json5-0.12.0.tar.gz", hash = "sha256:0b4b6ff56801a1c7dc817b0241bca4ce474a0e6a163bfef3fc594d3fd263ff3a", size = 51907, upload-time = "2025-04-03T16:33:13.201Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/41/9f/3500910d5a98549e3098807493851eeef2b89cdd3032227558a104dfe926/json5-0.12.0-py3-none-any.whl", hash = "sha256:6d37aa6c08b0609f16e1ec5ff94697e2cbbfbad5ac112afa05794da9ab7810db", size = 36079, upload-time = "2025-04-03T16:33:11.927Z" },
]
[[package]] [[package]]
name = "pillow" name = "pillow"
version = "10.2.0" version = "10.2.0"
@ -135,6 +144,7 @@ name = "scripts"
version = "1.0.0" version = "1.0.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "json5" },
{ name = "pillow" }, { name = "pillow" },
{ name = "polib" }, { name = "polib" },
{ name = "pydantic" }, { name = "pydantic" },
@ -142,6 +152,7 @@ dependencies = [
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "json5", specifier = ">=0.12.0" },
{ name = "pillow", specifier = "==10.2.0" }, { name = "pillow", specifier = "==10.2.0" },
{ name = "polib", specifier = ">=1.2.0" }, { name = "polib", specifier = ">=1.2.0" },
{ name = "pydantic", specifier = ">=2.11.7" }, { name = "pydantic", specifier = ">=2.11.7" },

View File

@ -1,18 +1,10 @@
import json, logging, ast, typing import logging, ast, typing
import common, bme import common, bme
from common import AssetKind from common import AssetKind
import pydantic import pydantic, json5
#region Assistant Checker #region Assistant Checker
# TODO:
# If possible, following check should be done.
# They are not done now because they are so complex to implement.
# - The reference to variables and functions in programmable fields.
# - The return type of prorgammable fields.
# - Texture name referred in the programmable field in Face.
# - In instance, passed params to instance is fulfilled.
def _try_add(entries: set[str], entry: str) -> bool: def _try_add(entries: set[str], entry: str) -> bool:
if entry in entries: if entry in entries:
@ -23,9 +15,6 @@ def _try_add(entries: set[str], entry: str) -> bool:
def _check_programmable_field(probe: str) -> None: def _check_programmable_field(probe: str) -> None:
# TODO:
# If possible, allow checking the reference to variables and function,
# to make sure the statement must can be executed.
try: try:
ast.parse(probe) ast.parse(probe)
except SyntaxError: except SyntaxError:
@ -178,7 +167,7 @@ def validate_jsons() -> None:
# Load all prototypes and check their basic format # Load all prototypes and check their basic format
prototypes: list[bme.Prototype] = [] prototypes: list[bme.Prototype] = []
for raw_json_file in raw_jsons_dir.glob('*.json'): for raw_json_file in raw_jsons_dir.glob('*.json5'):
# Skip non-file # Skip non-file
if not raw_json_file.is_file(): if not raw_json_file.is_file():
continue continue
@ -189,12 +178,12 @@ def validate_jsons() -> None:
# Load prototypes # Load prototypes
try: try:
with open(raw_json_file, 'r', encoding='utf-8') as f: with open(raw_json_file, 'r', encoding='utf-8') as f:
docuement = json.load(f) docuement = json5.load(f)
file_prototypes = bme.Prototypes.model_validate(docuement) file_prototypes = bme.Prototypes.model_validate(docuement)
except json.JSONDecodeError as e:
logging.error(f'File {raw_json_file} is not a valid JSON file. Reason: {e}')
except pydantic.ValidationError as e: except pydantic.ValidationError as e:
logging.error(f'JSON file {raw_json_file} lose essential fields. Detail: {e}') logging.error(f'JSON file {raw_json_file} lose essential fields. Detail: {e}')
except (ValueError, UnicodeDecodeError) as e:
logging.error(f'File {raw_json_file} is not a valid JSON5 file. Reason: {e}')
# Append all prototypes into list # Append all prototypes into list
prototypes += file_prototypes.root prototypes += file_prototypes.root