Make preserve original optional
This commit is contained in:
parent
032efac365
commit
9349374ab9
7 changed files with 1049 additions and 979 deletions
|
|
@ -1,283 +1,283 @@
|
||||||
"""
|
"""
|
||||||
Configuration Integration Helper
|
Configuration Integration Helper
|
||||||
Updates the actual module files with GUI configuration settings
|
Updates the actual module files with GUI configuration settings
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
|
|
||||||
|
|
||||||
class ConfigIntegration:
|
class ConfigIntegration:
|
||||||
"""Helper class to integrate GUI configuration with module files."""
|
"""Helper class to integrate GUI configuration with module files."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.modules_dir = Path("modules")
|
self.modules_dir = Path("modules")
|
||||||
|
|
||||||
def update_rpgmaker_config(self, config: Dict[str, Any]) -> bool:
|
def update_rpgmaker_config(self, config: Dict[str, Any]) -> bool:
|
||||||
"""Update rpgmakermvmz.py with configuration from GUI."""
|
"""Update rpgmakermvmz.py with configuration from GUI."""
|
||||||
module_path = self.modules_dir / "rpgmakermvmz.py"
|
module_path = self.modules_dir / "rpgmakermvmz.py"
|
||||||
|
|
||||||
if not module_path.exists():
|
if not module_path.exists():
|
||||||
raise FileNotFoundError(f"Module file not found: {module_path}")
|
raise FileNotFoundError(f"Module file not found: {module_path}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Read the current module file
|
# Read the current module file
|
||||||
with open(module_path, 'r', encoding='utf-8') as f:
|
with open(module_path, 'r', encoding='utf-8') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
|
|
||||||
# Update configuration values
|
# Update configuration values
|
||||||
updated_content = self._update_config_values(content, config)
|
updated_content = self._update_config_values(content, config)
|
||||||
|
|
||||||
# Write back to file
|
# Write back to file
|
||||||
with open(module_path, 'w', encoding='utf-8') as f:
|
with open(module_path, 'w', encoding='utf-8') as f:
|
||||||
f.write(updated_content)
|
f.write(updated_content)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
def _update_config_values(self, content: str, config: Dict[str, Any]) -> str:
|
def _update_config_values(self, content: str, config: Dict[str, Any]) -> str:
|
||||||
"""Update configuration values in the module content."""
|
"""Update configuration values in the module content."""
|
||||||
lines = content.split('\n')
|
lines = content.split('\n')
|
||||||
updated_lines = []
|
updated_lines = []
|
||||||
|
|
||||||
# Track which configurations we found and updated
|
# Track which configurations we found and updated
|
||||||
found_configs = set()
|
found_configs = set()
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
updated_line = line
|
updated_line = line
|
||||||
|
|
||||||
# Look for configuration assignments
|
# Look for configuration assignments
|
||||||
for config_key, config_value in config.items():
|
for config_key, config_value in config.items():
|
||||||
# Match lines like: CONFIG_NAME = value # comment
|
# Match lines like: CONFIG_NAME = value # comment
|
||||||
pattern = rf'^({re.escape(config_key)})\s*=\s*.*?(#.*)?$'
|
pattern = rf'^({re.escape(config_key)})\s*=\s*.*?(#.*)?$'
|
||||||
match = re.match(pattern, line.strip())
|
match = re.match(pattern, line.strip())
|
||||||
|
|
||||||
if match:
|
if match:
|
||||||
# Preserve the comment if it exists
|
# Preserve the comment if it exists
|
||||||
comment = match.group(2) if match.group(2) else ""
|
comment = match.group(2) if match.group(2) else ""
|
||||||
if comment:
|
if comment:
|
||||||
comment = " " + comment
|
comment = " " + comment
|
||||||
|
|
||||||
# Create the new line with proper formatting
|
# Create the new line with proper formatting
|
||||||
updated_line = f"{config_key} = {config_value}{comment}"
|
updated_line = f"{config_key} = {config_value}{comment}"
|
||||||
found_configs.add(config_key)
|
found_configs.add(config_key)
|
||||||
break
|
break
|
||||||
|
|
||||||
updated_lines.append(updated_line)
|
updated_lines.append(updated_line)
|
||||||
|
|
||||||
# Check if all configurations were found
|
# Check if all configurations were found
|
||||||
missing_configs = set(config.keys()) - found_configs
|
missing_configs = set(config.keys()) - found_configs
|
||||||
if missing_configs:
|
if missing_configs:
|
||||||
print(f"Warning: Could not find these configurations in module: {missing_configs}")
|
print(f"Warning: Could not find these configurations in module: {missing_configs}")
|
||||||
|
|
||||||
return '\n'.join(updated_lines)
|
return '\n'.join(updated_lines)
|
||||||
|
|
||||||
def update_env_file(self, config: Dict[str, Any], env_path: Path = None) -> bool:
|
def update_env_file(self, config: Dict[str, Any], env_path: Path = None) -> bool:
|
||||||
"""Update .env file with configuration."""
|
"""Update .env file with configuration."""
|
||||||
if env_path is None:
|
if env_path is None:
|
||||||
env_path = Path(".env")
|
env_path = Path(".env")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from dotenv import set_key
|
from dotenv import set_key
|
||||||
|
|
||||||
for key, value in config.items():
|
for key, value in config.items():
|
||||||
set_key(env_path, key, str(value))
|
set_key(env_path, key, str(value))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error updating .env file: {e}")
|
print(f"Error updating .env file: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def read_current_config(self, module_path: Path = None) -> Dict[str, Any]:
|
def read_current_config(self, module_path: Path = None) -> Dict[str, Any]:
|
||||||
"""Read current configuration from module file."""
|
"""Read current configuration from module file."""
|
||||||
if module_path is None:
|
if module_path is None:
|
||||||
module_path = self.modules_dir / "rpgmakermvmz.py"
|
module_path = self.modules_dir / "rpgmakermvmz.py"
|
||||||
|
|
||||||
if not module_path.exists():
|
if not module_path.exists():
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
config = {}
|
config = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(module_path, 'r', encoding='utf-8') as f:
|
with open(module_path, 'r', encoding='utf-8') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
|
|
||||||
# Find configuration lines used by the RPG Maker config UI.
|
# Find configuration lines used by the RPG Maker config UI.
|
||||||
bool_pattern = (
|
bool_pattern = (
|
||||||
r'^(FIRSTLINESPEAKERS|INLINE401SPEAKERS|FACENAME101|NAMES|'
|
r'^(FIRSTLINESPEAKERS|INLINE401SPEAKERS|FACENAME101|NAMES|'
|
||||||
r'BRFLAG|FIXTEXTWRAP|IGNORETLTEXT|TLSYSTEMVARIABLES|'
|
r'BRFLAG|FIXTEXTWRAP|IGNORETLTEXT|PRESERVEORIGINAL|TLSYSTEMVARIABLES|'
|
||||||
r'TLSYSTEMSWITCHES|JOIN408|SPEAKERS408|CODE\d+)\s*=\s*(True|False)'
|
r'TLSYSTEMSWITCHES|JOIN408|SPEAKERS408|CODE\d+)\s*=\s*(True|False)'
|
||||||
)
|
)
|
||||||
int_pattern = r'^(CODE122_VAR_MIN|CODE122_VAR_MAX)\s*=\s*(\d+)'
|
int_pattern = r'^(CODE122_VAR_MIN|CODE122_VAR_MAX)\s*=\s*(\d+)'
|
||||||
|
|
||||||
for line in content.split('\n'):
|
for line in content.split('\n'):
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
match = re.match(bool_pattern, line)
|
match = re.match(bool_pattern, line)
|
||||||
if match:
|
if match:
|
||||||
key = match.group(1)
|
key = match.group(1)
|
||||||
value = match.group(2) == 'True'
|
value = match.group(2) == 'True'
|
||||||
config[key] = value
|
config[key] = value
|
||||||
continue
|
continue
|
||||||
|
|
||||||
match = re.match(int_pattern, line)
|
match = re.match(int_pattern, line)
|
||||||
if match:
|
if match:
|
||||||
key = match.group(1)
|
key = match.group(1)
|
||||||
config[key] = int(match.group(2))
|
config[key] = int(match.group(2))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error reading configuration from {module_path}: {e}")
|
print(f"Error reading configuration from {module_path}: {e}")
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def read_plugin_config(self, module_path: Path = None) -> Dict[str, Any]:
|
def read_plugin_config(self, module_path: Path = None) -> Dict[str, Any]:
|
||||||
"""Read ENABLED_PLUGINS_357 and ENABLED_PATTERNS_355655 set literals from module file."""
|
"""Read ENABLED_PLUGINS_357 and ENABLED_PATTERNS_355655 set literals from module file."""
|
||||||
if module_path is None:
|
if module_path is None:
|
||||||
module_path = self.modules_dir / "rpgmakermvmz.py"
|
module_path = self.modules_dir / "rpgmakermvmz.py"
|
||||||
result: Dict[str, Any] = {
|
result: Dict[str, Any] = {
|
||||||
"ENABLED_PLUGINS_357": set(),
|
"ENABLED_PLUGINS_357": set(),
|
||||||
"ENABLED_PATTERNS_355655": set(),
|
"ENABLED_PATTERNS_355655": set(),
|
||||||
}
|
}
|
||||||
if not module_path.exists():
|
if not module_path.exists():
|
||||||
return result
|
return result
|
||||||
try:
|
try:
|
||||||
with open(module_path, "r", encoding="utf-8") as f:
|
with open(module_path, "r", encoding="utf-8") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
for var_name in ("ENABLED_PLUGINS_357", "ENABLED_PATTERNS_355655"):
|
for var_name in ("ENABLED_PLUGINS_357", "ENABLED_PATTERNS_355655"):
|
||||||
# Match: VAR_NAME: set = set() or VAR_NAME: set = {"a", "b"}
|
# Match: VAR_NAME: set = set() or VAR_NAME: set = {"a", "b"}
|
||||||
m = re.search(
|
m = re.search(
|
||||||
rf'^{re.escape(var_name)}\s*(?::\s*set)?\s*=\s*(\{{[^}}]*\}}|set\(\))',
|
rf'^{re.escape(var_name)}\s*(?::\s*set)?\s*=\s*(\{{[^}}]*\}}|set\(\))',
|
||||||
content,
|
content,
|
||||||
re.MULTILINE,
|
re.MULTILINE,
|
||||||
)
|
)
|
||||||
if m:
|
if m:
|
||||||
bracket = m.group(1)
|
bracket = m.group(1)
|
||||||
items = re.findall(r'"([^"]+)"|\'([^\']+)\'', bracket)
|
items = re.findall(r'"([^"]+)"|\'([^\']+)\'', bracket)
|
||||||
result[var_name] = {a or b for a, b in items}
|
result[var_name] = {a or b for a, b in items}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error reading plugin config: {e}")
|
print(f"Error reading plugin config: {e}")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def update_plugin_config(
|
def update_plugin_config(
|
||||||
self,
|
self,
|
||||||
enabled_357: set,
|
enabled_357: set,
|
||||||
enabled_355655: set,
|
enabled_355655: set,
|
||||||
module_path: Path = None,
|
module_path: Path = None,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Write ENABLED_PLUGINS_357 and ENABLED_PATTERNS_355655 set literals to module file."""
|
"""Write ENABLED_PLUGINS_357 and ENABLED_PATTERNS_355655 set literals to module file."""
|
||||||
if module_path is None:
|
if module_path is None:
|
||||||
module_path = self.modules_dir / "rpgmakermvmz.py"
|
module_path = self.modules_dir / "rpgmakermvmz.py"
|
||||||
if not module_path.exists():
|
if not module_path.exists():
|
||||||
raise FileNotFoundError(f"Module file not found: {module_path}")
|
raise FileNotFoundError(f"Module file not found: {module_path}")
|
||||||
|
|
||||||
def _fmt(s: set) -> str:
|
def _fmt(s: set) -> str:
|
||||||
if not s:
|
if not s:
|
||||||
return "set()"
|
return "set()"
|
||||||
items = ", ".join(f'"{k}"' for k in sorted(s))
|
items = ", ".join(f'"{k}"' for k in sorted(s))
|
||||||
return "{" + items + "}"
|
return "{" + items + "}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(module_path, "r", encoding="utf-8") as f:
|
with open(module_path, "r", encoding="utf-8") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
for var_name, val_set in (
|
for var_name, val_set in (
|
||||||
("ENABLED_PLUGINS_357", enabled_357),
|
("ENABLED_PLUGINS_357", enabled_357),
|
||||||
("ENABLED_PATTERNS_355655", enabled_355655),
|
("ENABLED_PATTERNS_355655", enabled_355655),
|
||||||
):
|
):
|
||||||
content = re.sub(
|
content = re.sub(
|
||||||
rf'^{re.escape(var_name)}\s*(?::\s*set)?\s*=\s*(?:\{{[^}}]*\}}|set\(\))',
|
rf'^{re.escape(var_name)}\s*(?::\s*set)?\s*=\s*(?:\{{[^}}]*\}}|set\(\))',
|
||||||
f'{var_name}: set = {_fmt(val_set)}',
|
f'{var_name}: set = {_fmt(val_set)}',
|
||||||
content,
|
content,
|
||||||
flags=re.MULTILINE | re.DOTALL,
|
flags=re.MULTILINE | re.DOTALL,
|
||||||
)
|
)
|
||||||
with open(module_path, "w", encoding="utf-8") as f:
|
with open(module_path, "w", encoding="utf-8") as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
def validate_module_syntax(self, module_path: Path = None) -> bool:
|
def validate_module_syntax(self, module_path: Path = None) -> bool:
|
||||||
"""Validate that the module file has correct Python syntax."""
|
"""Validate that the module file has correct Python syntax."""
|
||||||
if module_path is None:
|
if module_path is None:
|
||||||
module_path = self.modules_dir / "rpgmakermvmz.py"
|
module_path = self.modules_dir / "rpgmakermvmz.py"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(module_path, 'r', encoding='utf-8') as f:
|
with open(module_path, 'r', encoding='utf-8') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
|
|
||||||
# Try to compile the code
|
# Try to compile the code
|
||||||
compile(content, str(module_path), 'exec')
|
compile(content, str(module_path), 'exec')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except SyntaxError as e:
|
except SyntaxError as e:
|
||||||
print(f"Syntax error in {module_path}: {e}")
|
print(f"Syntax error in {module_path}: {e}")
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error validating {module_path}: {e}")
|
print(f"Error validating {module_path}: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def create_config_template(self) -> Dict[str, Any]:
|
def create_config_template(self) -> Dict[str, Any]:
|
||||||
"""Create a template configuration with default values."""
|
"""Create a template configuration with default values."""
|
||||||
return {
|
return {
|
||||||
# General config
|
# General config
|
||||||
"FIRSTLINESPEAKERS": False,
|
"FIRSTLINESPEAKERS": False,
|
||||||
"INLINE401SPEAKERS": False,
|
"INLINE401SPEAKERS": False,
|
||||||
"FACENAME101": False,
|
"FACENAME101": False,
|
||||||
"NAMES": False,
|
"NAMES": False,
|
||||||
"BRFLAG": False,
|
"BRFLAG": False,
|
||||||
"FIXTEXTWRAP": True,
|
"FIXTEXTWRAP": True,
|
||||||
"IGNORETLTEXT": False,
|
"IGNORETLTEXT": False,
|
||||||
|
|
||||||
# Main Codes
|
# Main Codes
|
||||||
"CODE401": True,
|
"CODE401": True,
|
||||||
"CODE405": True,
|
"CODE405": True,
|
||||||
"CODE102": True,
|
"CODE102": True,
|
||||||
|
|
||||||
# Optional codes
|
# Optional codes
|
||||||
"CODE101": False,
|
"CODE101": False,
|
||||||
"CODE408": False,
|
"CODE408": False,
|
||||||
|
|
||||||
# Variable codes
|
# Variable codes
|
||||||
"CODE122": False,
|
"CODE122": False,
|
||||||
|
|
||||||
# Other codes
|
# Other codes
|
||||||
"CODE355655": False,
|
"CODE355655": False,
|
||||||
"CODE357": False,
|
"CODE357": False,
|
||||||
"CODE657": False,
|
"CODE657": False,
|
||||||
"CODE356": False,
|
"CODE356": False,
|
||||||
"CODE320": False,
|
"CODE320": False,
|
||||||
"CODE324": False,
|
"CODE324": False,
|
||||||
"CODE111": False,
|
"CODE111": False,
|
||||||
"CODE108": False
|
"CODE108": False
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_config_descriptions(self) -> Dict[str, str]:
|
def get_config_descriptions(self) -> Dict[str, str]:
|
||||||
"""Get descriptions for all configuration options."""
|
"""Get descriptions for all configuration options."""
|
||||||
return {
|
return {
|
||||||
"FIRSTLINESPEAKERS": "If 1st line of 401 is a speaker, set to True",
|
"FIRSTLINESPEAKERS": "If 1st line of 401 is a speaker, set to True",
|
||||||
"INLINE401SPEAKERS": "Detect speaker from Name\u300cdialogue\u300d inline format in 401 lines",
|
"INLINE401SPEAKERS": "Detect speaker from Name\u300cdialogue\u300d inline format in 401 lines",
|
||||||
"FACENAME101": "Find Speakers in 101 Codes based on Face Name",
|
"FACENAME101": "Find Speakers in 101 Codes based on Face Name",
|
||||||
"NAMES": "Output a list of all the character names found",
|
"NAMES": "Output a list of all the character names found",
|
||||||
"BRFLAG": "If the game uses <br> instead of newlines",
|
"BRFLAG": "If the game uses <br> instead of newlines",
|
||||||
"FIXTEXTWRAP": "Overwrites textwrap for better formatting",
|
"FIXTEXTWRAP": "Overwrites textwrap for better formatting",
|
||||||
"IGNORETLTEXT": "Ignores all translated text",
|
"IGNORETLTEXT": "Ignores all translated text",
|
||||||
|
|
||||||
"CODE401": "Show Text - Main dialogue content",
|
"CODE401": "Show Text - Main dialogue content",
|
||||||
"CODE405": "Show Text (Scrolling) - Longer dialogue",
|
"CODE405": "Show Text (Scrolling) - Longer dialogue",
|
||||||
"CODE102": "Show Choices - Player choice options",
|
"CODE102": "Show Choices - Player choice options",
|
||||||
"CODE101": "Character Names - Turn on when names exist in 101",
|
"CODE101": "Character Names - Turn on when names exist in 101",
|
||||||
"CODE408": "Comments - WARNING: Can inflate costs significantly",
|
"CODE408": "Comments - WARNING: Can inflate costs significantly",
|
||||||
"CODE122": "Control Variables - Text stored in variables",
|
"CODE122": "Control Variables - Text stored in variables",
|
||||||
|
|
||||||
"CODE355655": "Scripts - Text within script commands",
|
"CODE355655": "Scripts - Text within script commands",
|
||||||
"CODE357": "Picture Text - Text displayed on pictures",
|
"CODE357": "Picture Text - Text displayed on pictures",
|
||||||
"CODE657": "Picture Text Extended - Extended picture text",
|
"CODE657": "Picture Text Extended - Extended picture text",
|
||||||
"CODE356": "Plugin Commands - Plugin command parameters",
|
"CODE356": "Plugin Commands - Plugin command parameters",
|
||||||
"CODE320": "Change Name Input - Name input prompts",
|
"CODE320": "Change Name Input - Name input prompts",
|
||||||
"CODE324": "Change Nickname - Nickname changes",
|
"CODE324": "Change Nickname - Nickname changes",
|
||||||
"CODE111": "Conditional Branch - Conditional text",
|
"CODE111": "Conditional Branch - Conditional text",
|
||||||
"CODE108": "Comments - Comment blocks"
|
"CODE108": "Comments - Comment blocks"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,8 @@ BRFLAG = False
|
||||||
FIXTEXTWRAP = True
|
FIXTEXTWRAP = True
|
||||||
# IGNORETLTEXT: Skip Translated Text.
|
# IGNORETLTEXT: Skip Translated Text.
|
||||||
IGNORETLTEXT = False
|
IGNORETLTEXT = False
|
||||||
|
# PRESERVEORIGINAL: Store Japanese source in _original fields (maps + database) for re-run safety.
|
||||||
|
PRESERVEORIGINAL = False
|
||||||
# TLSYSTEMVARIABLES: Translate System Variables. (Optional but sometimes necessary. Can break stuff.)
|
# TLSYSTEMVARIABLES: Translate System Variables. (Optional but sometimes necessary. Can break stuff.)
|
||||||
TLSYSTEMVARIABLES = False
|
TLSYSTEMVARIABLES = False
|
||||||
# TLSYSTEMSWITCHES: Translate System Switches. (Optional. Translates switch names in System.json.)
|
# TLSYSTEMSWITCHES: Translate System Switches. (Optional. Translates switch names in System.json.)
|
||||||
|
|
@ -487,6 +489,8 @@ def _group_raw_source(codeList, group_start: int, source_parts: list[str]) -> st
|
||||||
|
|
||||||
def _apply_original(cmd, raw_source: str) -> None:
|
def _apply_original(cmd, raw_source: str) -> None:
|
||||||
"""Set scalar _original only when not already present (re-run safe)."""
|
"""Set scalar _original only when not already present (re-run safe)."""
|
||||||
|
if not PRESERVEORIGINAL:
|
||||||
|
return
|
||||||
if not raw_source or not str(raw_source).strip():
|
if not raw_source or not str(raw_source).strip():
|
||||||
return
|
return
|
||||||
if _scalar_original(cmd) is not None:
|
if _scalar_original(cmd) is not None:
|
||||||
|
|
@ -510,6 +514,8 @@ def _choice_source(cmd, index: int) -> str:
|
||||||
|
|
||||||
def _apply_choice_original(cmd, index: int, raw_source: str) -> None:
|
def _apply_choice_original(cmd, index: int, raw_source: str) -> None:
|
||||||
"""Set _original[index] for code 102 only when that slot is empty."""
|
"""Set _original[index] for code 102 only when that slot is empty."""
|
||||||
|
if not PRESERVEORIGINAL:
|
||||||
|
return
|
||||||
if not raw_source or not str(raw_source).strip():
|
if not raw_source or not str(raw_source).strip():
|
||||||
return
|
return
|
||||||
params = cmd.get("parameters") or [[]]
|
params = cmd.get("parameters") or [[]]
|
||||||
|
|
@ -581,6 +587,8 @@ def _entry_field_source(entry, field: str) -> str:
|
||||||
|
|
||||||
def _apply_entry_field_original(entry, field: str, raw: str) -> None:
|
def _apply_entry_field_original(entry, field: str, raw: str) -> None:
|
||||||
"""Set _original[field] only when empty and raw contains Japanese."""
|
"""Set _original[field] only when empty and raw contains Japanese."""
|
||||||
|
if not PRESERVEORIGINAL:
|
||||||
|
return
|
||||||
if not isinstance(entry, dict) or not raw or not str(raw).strip():
|
if not isinstance(entry, dict) or not raw or not str(raw).strip():
|
||||||
return
|
return
|
||||||
if not re.search(LANGREGEX, raw):
|
if not re.search(LANGREGEX, raw):
|
||||||
|
|
@ -622,6 +630,8 @@ def _system_scalar_source(data, field: str) -> str:
|
||||||
|
|
||||||
def _apply_system_scalar_original(data, field: str, raw: str) -> None:
|
def _apply_system_scalar_original(data, field: str, raw: str) -> None:
|
||||||
"""Set root _original[field] only when empty and raw contains Japanese."""
|
"""Set root _original[field] only when empty and raw contains Japanese."""
|
||||||
|
if not PRESERVEORIGINAL:
|
||||||
|
return
|
||||||
if not raw or not str(raw).strip() or not re.search(LANGREGEX, raw):
|
if not raw or not str(raw).strip() or not re.search(LANGREGEX, raw):
|
||||||
return
|
return
|
||||||
orig = _system_orig(data)
|
orig = _system_orig(data)
|
||||||
|
|
@ -650,6 +660,8 @@ def _system_list_source(data, list_name: str, index: int) -> str:
|
||||||
|
|
||||||
def _apply_system_list_original(data, list_name: str, index: int, raw: str) -> None:
|
def _apply_system_list_original(data, list_name: str, index: int, raw: str) -> None:
|
||||||
"""Set _original[list_name][str(index)] only when empty and raw contains Japanese."""
|
"""Set _original[list_name][str(index)] only when empty and raw contains Japanese."""
|
||||||
|
if not PRESERVEORIGINAL:
|
||||||
|
return
|
||||||
if not raw or not str(raw).strip() or not re.search(LANGREGEX, raw):
|
if not raw or not str(raw).strip() or not re.search(LANGREGEX, raw):
|
||||||
return
|
return
|
||||||
orig = _system_orig(data)
|
orig = _system_orig(data)
|
||||||
|
|
@ -685,6 +697,8 @@ def _system_terms_source(data, category: str, index: int) -> str:
|
||||||
|
|
||||||
def _apply_system_terms_original(data, category: str, index: int, raw: str) -> None:
|
def _apply_system_terms_original(data, category: str, index: int, raw: str) -> None:
|
||||||
"""Set _original.terms[category][str(index)] only when empty and raw contains Japanese."""
|
"""Set _original.terms[category][str(index)] only when empty and raw contains Japanese."""
|
||||||
|
if not PRESERVEORIGINAL:
|
||||||
|
return
|
||||||
if not raw or not str(raw).strip() or not re.search(LANGREGEX, raw):
|
if not raw or not str(raw).strip() or not re.search(LANGREGEX, raw):
|
||||||
return
|
return
|
||||||
orig = _system_orig(data)
|
orig = _system_orig(data)
|
||||||
|
|
@ -725,6 +739,8 @@ def _system_terms_message_source(data, key: str) -> str:
|
||||||
|
|
||||||
def _apply_system_terms_message_original(data, key: str, raw: str) -> None:
|
def _apply_system_terms_message_original(data, key: str, raw: str) -> None:
|
||||||
"""Set _original.terms.messages[key] only when empty and raw contains Japanese."""
|
"""Set _original.terms.messages[key] only when empty and raw contains Japanese."""
|
||||||
|
if not PRESERVEORIGINAL:
|
||||||
|
return
|
||||||
if not raw or not str(raw).strip() or not re.search(LANGREGEX, raw):
|
if not raw or not str(raw).strip() or not re.search(LANGREGEX, raw):
|
||||||
return
|
return
|
||||||
orig = _system_orig(data)
|
orig = _system_orig(data)
|
||||||
|
|
|
||||||
117
set_defaults.py
117
set_defaults.py
|
|
@ -1,58 +1,59 @@
|
||||||
import re
|
import re
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
# Canonical defaults used by the GUI and the `set_defaults` script.
|
# Canonical defaults used by the GUI and the `set_defaults` script.
|
||||||
# Stored as booleans for easier consumption by the GUI.
|
# Stored as booleans for easier consumption by the GUI.
|
||||||
DEFAULTS = {
|
DEFAULTS = {
|
||||||
'FIRSTLINESPEAKERS': False,
|
'FIRSTLINESPEAKERS': False,
|
||||||
'FACENAME101': False,
|
'FACENAME101': False,
|
||||||
'BRFLAG': False,
|
'BRFLAG': False,
|
||||||
'FIXTEXTWRAP': True,
|
'FIXTEXTWRAP': True,
|
||||||
'IGNORETLTEXT': False,
|
'IGNORETLTEXT': False,
|
||||||
'JOIN408': False,
|
'PRESERVEORIGINAL': True,
|
||||||
# Speakers / Dialogue / Scroll / Choices (Main Codes)
|
'JOIN408': False,
|
||||||
'CODE101': True,
|
# Speakers / Dialogue / Scroll / Choices (Main Codes)
|
||||||
'CODE401': True,
|
'CODE101': True,
|
||||||
'CODE405': True,
|
'CODE401': True,
|
||||||
'CODE102': True,
|
'CODE405': True,
|
||||||
# Optional
|
'CODE102': True,
|
||||||
'CODE408': False,
|
# Optional
|
||||||
# Variables
|
'CODE408': False,
|
||||||
'CODE122': False,
|
# Variables
|
||||||
# Other
|
'CODE122': False,
|
||||||
'CODE355655': False,
|
# Other
|
||||||
'CODE357': False,
|
'CODE355655': False,
|
||||||
'CODE657': False,
|
'CODE357': False,
|
||||||
'CODE356': False,
|
'CODE657': False,
|
||||||
'CODE320': False,
|
'CODE356': False,
|
||||||
'CODE324': False,
|
'CODE320': False,
|
||||||
'CODE111': False,
|
'CODE324': False,
|
||||||
'CODE108': False
|
'CODE111': False,
|
||||||
}
|
'CODE108': False
|
||||||
|
}
|
||||||
|
|
||||||
def get_defaults():
|
|
||||||
"""Return a shallow copy of the DEFAULTS dictionary."""
|
def get_defaults():
|
||||||
return deepcopy(DEFAULTS)
|
"""Return a shallow copy of the DEFAULTS dictionary."""
|
||||||
|
return deepcopy(DEFAULTS)
|
||||||
|
|
||||||
def set_defaults(file_path):
|
|
||||||
"""Write canonical default values into a module file.
|
def set_defaults(file_path):
|
||||||
|
"""Write canonical default values into a module file.
|
||||||
The module file contains assignments like `CODE401 = True`. This
|
|
||||||
function replaces those assignment lines with the canonical defaults.
|
The module file contains assignments like `CODE401 = True`. This
|
||||||
"""
|
function replaces those assignment lines with the canonical defaults.
|
||||||
with open(file_path, 'r', encoding='utf-8') as file:
|
"""
|
||||||
content = file.read()
|
with open(file_path, 'r', encoding='utf-8') as file:
|
||||||
|
content = file.read()
|
||||||
# Update the content with the default values (converted to Python literals)
|
|
||||||
for key, value in DEFAULTS.items():
|
# Update the content with the default values (converted to Python literals)
|
||||||
value_str = 'True' if value else 'False'
|
for key, value in DEFAULTS.items():
|
||||||
content = re.sub(rf'{re.escape(key)}\s*=\s*.*', f'{key} = {value_str}', content)
|
value_str = 'True' if value else 'False'
|
||||||
|
content = re.sub(rf'{re.escape(key)}\s*=\s*.*', f'{key} = {value_str}', content)
|
||||||
with open(file_path, 'w', encoding='utf-8') as file:
|
|
||||||
file.write(content)
|
with open(file_path, 'w', encoding='utf-8') as file:
|
||||||
|
file.write(content)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
set_defaults('modules/rpgmakermvmz.py')
|
if __name__ == "__main__":
|
||||||
|
set_defaults('modules/rpgmakermvmz.py')
|
||||||
|
|
|
||||||
146
tests/fixtures/README.md
vendored
146
tests/fixtures/README.md
vendored
|
|
@ -1,69 +1,77 @@
|
||||||
# Map `_original` fixture
|
# Map `_original` fixture
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
||||||
- **`Map_original_fixture.json`** — Minimal RPG Maker MV map (17×13) with one event exercising every `_original` code path: 401, 405, 102, 101, and 122.
|
- **`Map_original_fixture.json`** — Minimal RPG Maker MV map (17×13) with one event exercising every `_original` code path: 401, 405, 102, 101, and 122.
|
||||||
- **`Map_original_fixture_manifest.json`** — Expected `_original` values after translation (used by tests).
|
- **`Map_original_fixture_manifest.json`** — Expected `_original` values after translation (used by tests).
|
||||||
|
|
||||||
## Event layout (event id 1, page 0)
|
## Event layout (event id 1, page 0)
|
||||||
|
|
||||||
| Order | Code | Purpose |
|
| Order | Code | Purpose |
|
||||||
|------:|------|---------|
|
|------:|------|---------|
|
||||||
| 1–2 | 401 | Color speaker line `\C[2]テスト子\C[0]` + following dialogue |
|
| 1–2 | 401 | Color speaker line `\C[2]テスト子\C[0]` + following dialogue |
|
||||||
| 3–4 | 401 | Merged multi-line dialogue (two 401s → one batch) |
|
| 3–4 | 401 | Merged multi-line dialogue (two 401s → one batch) |
|
||||||
| 5–6 | 101, 405 | Empty face setup + scrolling text line |
|
| 5–6 | 101, 405 | Empty face setup + scrolling text line |
|
||||||
| 7 | 102 | Three choices (middle choice has `if(...)` prefix) |
|
| 7 | 102 | Three choices (middle choice has `if(...)` prefix) |
|
||||||
| 8–9 | 108, 408 | Choice-help marker (`選択肢ヘルプ`) + help comment line |
|
| 8–9 | 108, 408 | Choice-help marker (`選択肢ヘルプ`) + help comment line |
|
||||||
| 10–11 | 101, 401 | Name box with `\C[2]アリス\C[0]` + dialogue |
|
| 10–11 | 101, 401 | Name box with `\C[2]アリス\C[0]` + dialogue |
|
||||||
| 10–11 | 122 | Variable string `` `変数の中身` `` and `` `セミコロン`; `` |
|
| 10–11 | 122 | Variable string `` `変数の中身` `` and `` `セミコロン`; `` |
|
||||||
|
|
||||||
## Run the test
|
## Run the test
|
||||||
|
|
||||||
Using the project venv (recommended):
|
Using the project venv (recommended):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./tests/run_tests.sh
|
./tests/run_tests.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Or explicitly:
|
Or explicitly:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./tests/run_tests.sh tests.test_mvmz_source_original -v
|
./tests/run_tests.sh tests.test_mvmz_source_original -v
|
||||||
./tests/run_tests.sh tests.test_mvmz_source_original.TestFixtureMapOriginal -v
|
./tests/run_tests.sh tests.test_mvmz_source_original.TestFixtureMapOriginal -v
|
||||||
```
|
```
|
||||||
|
|
||||||
Manual equivalent (must run from project root, with venv activated):
|
Manual equivalent (must run from project root, with venv activated):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /path/to/DazedMTLTool
|
cd /path/to/DazedMTLTool
|
||||||
source .venv/bin/activate # or: source venv/bin/activate
|
source .venv/bin/activate # or: source venv/bin/activate
|
||||||
python -m unittest tests.test_mvmz_source_original -v
|
python -m unittest tests.test_mvmz_source_original -v
|
||||||
```
|
```
|
||||||
|
|
||||||
Do **not** use `pytest` unless you install it yourself — this project uses the stdlib `unittest` runner.
|
Do **not** use `pytest` unless you install it yourself — this project uses the stdlib `unittest` runner.
|
||||||
|
|
||||||
If you see `ModuleNotFoundError: No module named 'colorama'` (or similar), activate the venv or use `./tests/run_tests.sh` instead of system `python3`.
|
If you see `ModuleNotFoundError: No module named 'colorama'` (or similar), activate the venv or use `./tests/run_tests.sh` instead of system `python3`.
|
||||||
|
|
||||||
Tests mock `translateAI` / `getSpeaker` — no API key required. Enable `CODE122` for the duration of the run.
|
Tests mock `translateAI` / `getSpeaker` — no API key required.
|
||||||
|
|
||||||
## Database `_original` fixtures
|
### Optional `_original` config (in `modules/rpgmakermvmz.py`)
|
||||||
|
|
||||||
Mini database JSON files for scalar field preservation (notes deferred to Phase 2):
|
| Flag | Scope | Default |
|
||||||
|
|------|-------|---------|
|
||||||
| Fixture | Parser | Fields |
|
| `PRESERVEORIGINAL` | Map/event commands and database JSON | `True` |
|
||||||
|---------|--------|--------|
|
|
||||||
| `Actors_original_fixture.json` | `searchNames` | `name`, `nickname`, `profile` |
|
Set to `False` to skip writing `_original`. Existing `_original` keys are still read on re-run.
|
||||||
| `Items_original_fixture.json` | `searchNames` | `name`, `description` |
|
|
||||||
| `Skills_original_fixture.json` | `searchNames` | `name`, `description`, `message1` |
|
## Database `_original` fixtures
|
||||||
| `States_original_fixture.json` | `searchSS` | `name`, `description`, `message1` |
|
|
||||||
| `System_original_fixture.json` | `searchSystem` | `gameTitle`, `terms.basic[1]`, `armorTypes[1]`, `terms.messages.alwaysDash` |
|
Mini database JSON files for scalar field preservation (notes deferred to Phase 2):
|
||||||
|
|
||||||
Expected `_original` shapes are in `db_original_manifest.json`.
|
| Fixture | Parser | Fields |
|
||||||
|
|---------|--------|--------|
|
||||||
```bash
|
| `Actors_original_fixture.json` | `searchNames` | `name`, `nickname`, `profile` |
|
||||||
./tests/run_tests.sh tests.test_mvmz_db_original -v
|
| `Items_original_fixture.json` | `searchNames` | `name`, `description` |
|
||||||
```
|
| `Skills_original_fixture.json` | `searchNames` | `name`, `description`, `message1` |
|
||||||
|
| `States_original_fixture.json` | `searchSS` | `name`, `description`, `message1` |
|
||||||
## Manual check
|
| `System_original_fixture.json` | `searchSystem` | `gameTitle`, `terms.basic[1]`, `armorTypes[1]`, `terms.messages.alwaysDash` |
|
||||||
|
|
||||||
Copy the fixture into `files/` and run the tool with batch/consume or live translate, then diff against the manifest’s `expected_original` fields in `translated/Map_original_fixture.json`.
|
Expected `_original` shapes are in `db_original_manifest.json`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./tests/run_tests.sh tests.test_mvmz_db_original -v
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manual check
|
||||||
|
|
||||||
|
Copy the fixture into `files/` and run the tool with batch/consume or live translate, then diff against the manifest’s `expected_original` fields in `translated/Map_original_fixture.json`.
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ if ! "$PYTHON" -c "import colorama, dotenv, tqdm" >/dev/null 2>&1; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$#" -eq 0 ]]; then
|
if [[ "$#" -eq 0 ]]; then
|
||||||
set -- tests.test_mvmz_source_original -v
|
set -- discover -s tests -p 'test_mvmz_*.py' -v
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec "$PYTHON" -m unittest "$@"
|
exec "$PYTHON" -m unittest "$@"
|
||||||
|
|
|
||||||
|
|
@ -1,205 +1,222 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Integration tests for _original source preservation in database parsers."""
|
"""Integration tests for _original source preservation in database parsers."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
os.chdir(ROOT)
|
os.chdir(ROOT)
|
||||||
sys.path.insert(0, str(ROOT))
|
sys.path.insert(0, str(ROOT))
|
||||||
|
|
||||||
import modules.rpgmakermvmz as mvmz # noqa: E402
|
import modules.rpgmakermvmz as mvmz # noqa: E402
|
||||||
|
|
||||||
LANGREGEX = mvmz.LANGREGEX
|
LANGREGEX = mvmz.LANGREGEX
|
||||||
FIXTURES = ROOT / "tests" / "fixtures"
|
FIXTURES = ROOT / "tests" / "fixtures"
|
||||||
MANIFEST = json.loads((FIXTURES / "db_original_manifest.json").read_text(encoding="utf-8"))
|
MANIFEST = json.loads((FIXTURES / "db_original_manifest.json").read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
|
||||||
def _mock_translate(text, history, batch=False):
|
def _mock_translate(text, history, batch=False):
|
||||||
def tr(s):
|
def tr(s):
|
||||||
if not isinstance(s, str):
|
if not isinstance(s, str):
|
||||||
return s
|
return s
|
||||||
return "EN_TRANSLATED"
|
return "EN_TRANSLATED"
|
||||||
|
|
||||||
if isinstance(text, list):
|
if isinstance(text, list):
|
||||||
return [[tr(t) for t in text], [0, 0]]
|
return [[tr(t) for t in text], [0, 0]]
|
||||||
return [tr(text), [0, 0]]
|
return [tr(text), [0, 0]]
|
||||||
|
|
||||||
|
|
||||||
def _has_japanese(s: str) -> bool:
|
def _has_japanese(s: str) -> bool:
|
||||||
return bool(re.search(LANGREGEX, s or ""))
|
return bool(re.search(LANGREGEX, s or ""))
|
||||||
|
|
||||||
|
|
||||||
def _run_search_names(data, context, filename):
|
def _run_search_names(data, context, filename, *, preserve_original=True):
|
||||||
captured = []
|
captured = []
|
||||||
|
|
||||||
def translate(text, history, batch=False):
|
def translate(text, history, batch=False):
|
||||||
captured.append(copy.deepcopy(text))
|
captured.append(copy.deepcopy(text))
|
||||||
return _mock_translate(text, history, batch)
|
return _mock_translate(text, history, batch)
|
||||||
|
|
||||||
orig_t = mvmz.translateAI
|
orig_t = mvmz.translateAI
|
||||||
orig_vocab = mvmz.update_vocab_section
|
orig_vocab = mvmz.update_vocab_section
|
||||||
mvmz.translateAI = translate
|
orig_preserve = mvmz.PRESERVEORIGINAL
|
||||||
mvmz.update_vocab_section = lambda *args, **kwargs: None
|
mvmz.translateAI = translate
|
||||||
try:
|
mvmz.update_vocab_section = lambda *args, **kwargs: None
|
||||||
data_copy = copy.deepcopy(data)
|
mvmz.PRESERVEORIGINAL = preserve_original
|
||||||
mvmz.searchNames(data_copy, None, context, filename)
|
try:
|
||||||
return data_copy, captured
|
data_copy = copy.deepcopy(data)
|
||||||
finally:
|
mvmz.searchNames(data_copy, None, context, filename)
|
||||||
mvmz.translateAI = orig_t
|
return data_copy, captured
|
||||||
mvmz.update_vocab_section = orig_vocab
|
finally:
|
||||||
|
mvmz.translateAI = orig_t
|
||||||
|
mvmz.update_vocab_section = orig_vocab
|
||||||
def _run_search_ss(state):
|
mvmz.PRESERVEORIGINAL = orig_preserve
|
||||||
captured = []
|
|
||||||
|
|
||||||
def translate(text, history, batch=False):
|
def _run_search_ss(state):
|
||||||
captured.append(copy.deepcopy(text))
|
captured = []
|
||||||
return _mock_translate(text, history, batch)
|
|
||||||
|
def translate(text, history, batch=False):
|
||||||
orig_t = mvmz.translateAI
|
captured.append(copy.deepcopy(text))
|
||||||
mvmz.translateAI = translate
|
return _mock_translate(text, history, batch)
|
||||||
try:
|
|
||||||
state_copy = copy.deepcopy(state)
|
orig_t = mvmz.translateAI
|
||||||
mvmz.searchSS(state_copy, None)
|
orig_preserve = mvmz.PRESERVEORIGINAL
|
||||||
return state_copy, captured
|
mvmz.translateAI = translate
|
||||||
finally:
|
mvmz.PRESERVEORIGINAL = True
|
||||||
mvmz.translateAI = orig_t
|
try:
|
||||||
|
state_copy = copy.deepcopy(state)
|
||||||
|
mvmz.searchSS(state_copy, None)
|
||||||
def _run_search_system(data):
|
return state_copy, captured
|
||||||
captured = []
|
finally:
|
||||||
|
mvmz.translateAI = orig_t
|
||||||
def translate(text, history, batch=False):
|
mvmz.PRESERVEORIGINAL = orig_preserve
|
||||||
captured.append(copy.deepcopy(text))
|
|
||||||
return _mock_translate(text, history, batch)
|
|
||||||
|
def _run_search_system(data):
|
||||||
orig_t = mvmz.translateAI
|
captured = []
|
||||||
mvmz.translateAI = translate
|
|
||||||
try:
|
def translate(text, history, batch=False):
|
||||||
data_copy = copy.deepcopy(data)
|
captured.append(copy.deepcopy(text))
|
||||||
mvmz.searchSystem(data_copy, None)
|
return _mock_translate(text, history, batch)
|
||||||
return data_copy, captured
|
|
||||||
finally:
|
orig_t = mvmz.translateAI
|
||||||
mvmz.translateAI = orig_t
|
orig_preserve = mvmz.PRESERVEORIGINAL
|
||||||
|
mvmz.translateAI = translate
|
||||||
|
mvmz.PRESERVEORIGINAL = True
|
||||||
def _assert_batches_japanese(captured):
|
try:
|
||||||
for payload in captured:
|
data_copy = copy.deepcopy(data)
|
||||||
items = payload if isinstance(payload, list) else [payload]
|
mvmz.searchSystem(data_copy, None)
|
||||||
for item in items:
|
return data_copy, captured
|
||||||
if not isinstance(item, str) or item == "EN_TRANSLATED":
|
finally:
|
||||||
continue
|
mvmz.translateAI = orig_t
|
||||||
if item.startswith("Taro"):
|
mvmz.PRESERVEORIGINAL = orig_preserve
|
||||||
item = item[4:]
|
|
||||||
self_fail = not _has_japanese(item)
|
|
||||||
if self_fail:
|
def _assert_batches_japanese(captured):
|
||||||
raise AssertionError(f"Re-run sent non-Japanese to translateAI: {item!r}")
|
for payload in captured:
|
||||||
|
items = payload if isinstance(payload, list) else [payload]
|
||||||
|
for item in items:
|
||||||
class TestActorsOriginal(unittest.TestCase):
|
if not isinstance(item, str) or item == "EN_TRANSLATED":
|
||||||
def test_first_pass_writes_original(self):
|
continue
|
||||||
data = json.loads((FIXTURES / "Actors_original_fixture.json").read_text(encoding="utf-8"))
|
if item.startswith("Taro"):
|
||||||
result, _ = _run_search_names(data, "Actors", "Actors.json")
|
item = item[4:]
|
||||||
entry = result[MANIFEST["actors"]["entry_index"]]
|
self_fail = not _has_japanese(item)
|
||||||
expected = MANIFEST["actors"]["expected_original"]
|
if self_fail:
|
||||||
self.assertEqual(entry.get("_original"), expected)
|
raise AssertionError(f"Re-run sent non-Japanese to translateAI: {item!r}")
|
||||||
self.assertNotEqual(entry["name"], expected["name"])
|
|
||||||
self.assertNotEqual(entry["nickname"], expected["nickname"])
|
|
||||||
self.assertNotEqual(entry["profile"], expected["profile"])
|
class TestActorsOriginal(unittest.TestCase):
|
||||||
|
def test_first_pass_writes_original(self):
|
||||||
def test_rerun_preserves_original(self):
|
data = json.loads((FIXTURES / "Actors_original_fixture.json").read_text(encoding="utf-8"))
|
||||||
data = json.loads((FIXTURES / "Actors_original_fixture.json").read_text(encoding="utf-8"))
|
result, _ = _run_search_names(data, "Actors", "Actors.json")
|
||||||
result1, _ = _run_search_names(data, "Actors", "Actors.json")
|
entry = result[MANIFEST["actors"]["entry_index"]]
|
||||||
orig_snapshot = copy.deepcopy(result1[1]["_original"])
|
expected = MANIFEST["actors"]["expected_original"]
|
||||||
result2, captured2 = _run_search_names(result1, "Actors", "Actors.json")
|
self.assertEqual(entry.get("_original"), expected)
|
||||||
self.assertEqual(result2[1]["_original"], orig_snapshot)
|
self.assertNotEqual(entry["name"], expected["name"])
|
||||||
_assert_batches_japanese(captured2)
|
self.assertNotEqual(entry["nickname"], expected["nickname"])
|
||||||
|
self.assertNotEqual(entry["profile"], expected["profile"])
|
||||||
|
|
||||||
class TestItemsOriginal(unittest.TestCase):
|
def test_rerun_preserves_original(self):
|
||||||
def test_first_pass_writes_original(self):
|
data = json.loads((FIXTURES / "Actors_original_fixture.json").read_text(encoding="utf-8"))
|
||||||
data = json.loads((FIXTURES / "Items_original_fixture.json").read_text(encoding="utf-8"))
|
result1, _ = _run_search_names(data, "Actors", "Actors.json")
|
||||||
result, _ = _run_search_names(data, "Items", "Items.json")
|
orig_snapshot = copy.deepcopy(result1[1]["_original"])
|
||||||
entry = result[MANIFEST["items"]["entry_index"]]
|
result2, captured2 = _run_search_names(result1, "Actors", "Actors.json")
|
||||||
expected = MANIFEST["items"]["expected_original"]
|
self.assertEqual(result2[1]["_original"], orig_snapshot)
|
||||||
self.assertEqual(entry.get("_original"), expected)
|
_assert_batches_japanese(captured2)
|
||||||
self.assertNotEqual(entry["name"], expected["name"])
|
|
||||||
self.assertNotEqual(entry["description"], expected["description"])
|
|
||||||
|
class TestItemsOriginal(unittest.TestCase):
|
||||||
def test_rerun_preserves_original(self):
|
def test_first_pass_writes_original(self):
|
||||||
data = json.loads((FIXTURES / "Items_original_fixture.json").read_text(encoding="utf-8"))
|
data = json.loads((FIXTURES / "Items_original_fixture.json").read_text(encoding="utf-8"))
|
||||||
result1, _ = _run_search_names(data, "Items", "Items.json")
|
result, _ = _run_search_names(data, "Items", "Items.json")
|
||||||
orig_snapshot = copy.deepcopy(result1[1]["_original"])
|
entry = result[MANIFEST["items"]["entry_index"]]
|
||||||
result2, captured2 = _run_search_names(result1, "Items", "Items.json")
|
expected = MANIFEST["items"]["expected_original"]
|
||||||
self.assertEqual(result2[1]["_original"], orig_snapshot)
|
self.assertEqual(entry.get("_original"), expected)
|
||||||
_assert_batches_japanese(captured2)
|
self.assertNotEqual(entry["name"], expected["name"])
|
||||||
|
self.assertNotEqual(entry["description"], expected["description"])
|
||||||
|
|
||||||
class TestSkillsOriginal(unittest.TestCase):
|
def test_rerun_preserves_original(self):
|
||||||
def test_first_pass_writes_original(self):
|
data = json.loads((FIXTURES / "Items_original_fixture.json").read_text(encoding="utf-8"))
|
||||||
data = json.loads((FIXTURES / "Skills_original_fixture.json").read_text(encoding="utf-8"))
|
result1, _ = _run_search_names(data, "Items", "Items.json")
|
||||||
result, _ = _run_search_names(data, "Skills", "Skills.json")
|
orig_snapshot = copy.deepcopy(result1[1]["_original"])
|
||||||
entry = result[MANIFEST["skills"]["entry_index"]]
|
result2, captured2 = _run_search_names(result1, "Items", "Items.json")
|
||||||
expected = MANIFEST["skills"]["expected_original"]
|
self.assertEqual(result2[1]["_original"], orig_snapshot)
|
||||||
self.assertEqual(entry.get("_original"), expected)
|
_assert_batches_japanese(captured2)
|
||||||
for field, jp in expected.items():
|
|
||||||
self.assertNotEqual(entry[field], jp)
|
|
||||||
|
class TestSkillsOriginal(unittest.TestCase):
|
||||||
def test_rerun_preserves_original(self):
|
def test_first_pass_writes_original(self):
|
||||||
data = json.loads((FIXTURES / "Skills_original_fixture.json").read_text(encoding="utf-8"))
|
data = json.loads((FIXTURES / "Skills_original_fixture.json").read_text(encoding="utf-8"))
|
||||||
result1, _ = _run_search_names(data, "Skills", "Skills.json")
|
result, _ = _run_search_names(data, "Skills", "Skills.json")
|
||||||
orig_snapshot = copy.deepcopy(result1[1]["_original"])
|
entry = result[MANIFEST["skills"]["entry_index"]]
|
||||||
result2, captured2 = _run_search_names(result1, "Skills", "Items.json")
|
expected = MANIFEST["skills"]["expected_original"]
|
||||||
self.assertEqual(result2[1]["_original"], orig_snapshot)
|
self.assertEqual(entry.get("_original"), expected)
|
||||||
_assert_batches_japanese(captured2)
|
for field, jp in expected.items():
|
||||||
|
self.assertNotEqual(entry[field], jp)
|
||||||
|
|
||||||
class TestStatesOriginal(unittest.TestCase):
|
def test_rerun_preserves_original(self):
|
||||||
def test_first_pass_writes_original(self):
|
data = json.loads((FIXTURES / "Skills_original_fixture.json").read_text(encoding="utf-8"))
|
||||||
data = json.loads((FIXTURES / "States_original_fixture.json").read_text(encoding="utf-8"))
|
result1, _ = _run_search_names(data, "Skills", "Skills.json")
|
||||||
state = data[MANIFEST["states"]["entry_index"]]
|
orig_snapshot = copy.deepcopy(result1[1]["_original"])
|
||||||
result, _ = _run_search_ss(state)
|
result2, captured2 = _run_search_names(result1, "Skills", "Items.json")
|
||||||
expected = MANIFEST["states"]["expected_original"]
|
self.assertEqual(result2[1]["_original"], orig_snapshot)
|
||||||
self.assertEqual(result.get("_original"), expected)
|
_assert_batches_japanese(captured2)
|
||||||
for field, jp in expected.items():
|
|
||||||
self.assertNotEqual(result[field], jp)
|
|
||||||
|
class TestStatesOriginal(unittest.TestCase):
|
||||||
def test_rerun_preserves_original(self):
|
def test_first_pass_writes_original(self):
|
||||||
data = json.loads((FIXTURES / "States_original_fixture.json").read_text(encoding="utf-8"))
|
data = json.loads((FIXTURES / "States_original_fixture.json").read_text(encoding="utf-8"))
|
||||||
state = data[MANIFEST["states"]["entry_index"]]
|
state = data[MANIFEST["states"]["entry_index"]]
|
||||||
result1, _ = _run_search_ss(state)
|
result, _ = _run_search_ss(state)
|
||||||
orig_snapshot = copy.deepcopy(result1["_original"])
|
expected = MANIFEST["states"]["expected_original"]
|
||||||
result2, captured2 = _run_search_ss(result1)
|
self.assertEqual(result.get("_original"), expected)
|
||||||
self.assertEqual(result2["_original"], orig_snapshot)
|
for field, jp in expected.items():
|
||||||
_assert_batches_japanese(captured2)
|
self.assertNotEqual(result[field], jp)
|
||||||
|
|
||||||
|
def test_rerun_preserves_original(self):
|
||||||
class TestSystemOriginal(unittest.TestCase):
|
data = json.loads((FIXTURES / "States_original_fixture.json").read_text(encoding="utf-8"))
|
||||||
def test_first_pass_writes_original(self):
|
state = data[MANIFEST["states"]["entry_index"]]
|
||||||
data = json.loads((FIXTURES / "System_original_fixture.json").read_text(encoding="utf-8"))
|
result1, _ = _run_search_ss(state)
|
||||||
result, _ = _run_search_system(data)
|
orig_snapshot = copy.deepcopy(result1["_original"])
|
||||||
expected = MANIFEST["system"]["expected_original"]
|
result2, captured2 = _run_search_ss(result1)
|
||||||
self.assertEqual(result.get("_original"), expected)
|
self.assertEqual(result2["_original"], orig_snapshot)
|
||||||
self.assertNotEqual(result["gameTitle"], expected["gameTitle"])
|
_assert_batches_japanese(captured2)
|
||||||
self.assertNotEqual(result["terms"]["basic"][1], expected["terms"]["basic"]["1"])
|
|
||||||
self.assertNotEqual(result["armorTypes"][1], expected["armorTypes"]["1"])
|
|
||||||
|
class TestSystemOriginal(unittest.TestCase):
|
||||||
def test_rerun_preserves_original(self):
|
def test_first_pass_writes_original(self):
|
||||||
data = json.loads((FIXTURES / "System_original_fixture.json").read_text(encoding="utf-8"))
|
data = json.loads((FIXTURES / "System_original_fixture.json").read_text(encoding="utf-8"))
|
||||||
result1, _ = _run_search_system(data)
|
result, _ = _run_search_system(data)
|
||||||
orig_snapshot = copy.deepcopy(result1["_original"])
|
expected = MANIFEST["system"]["expected_original"]
|
||||||
result2, captured2 = _run_search_system(result1)
|
self.assertEqual(result.get("_original"), expected)
|
||||||
self.assertEqual(result2["_original"], orig_snapshot)
|
self.assertNotEqual(result["gameTitle"], expected["gameTitle"])
|
||||||
_assert_batches_japanese(captured2)
|
self.assertNotEqual(result["terms"]["basic"][1], expected["terms"]["basic"]["1"])
|
||||||
|
self.assertNotEqual(result["armorTypes"][1], expected["armorTypes"]["1"])
|
||||||
|
|
||||||
if __name__ == "__main__":
|
def test_rerun_preserves_original(self):
|
||||||
unittest.main(verbosity=2)
|
data = json.loads((FIXTURES / "System_original_fixture.json").read_text(encoding="utf-8"))
|
||||||
|
result1, _ = _run_search_system(data)
|
||||||
|
orig_snapshot = copy.deepcopy(result1["_original"])
|
||||||
|
result2, captured2 = _run_search_system(result1)
|
||||||
|
self.assertEqual(result2["_original"], orig_snapshot)
|
||||||
|
_assert_batches_japanese(captured2)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPreserveOriginalDisabled(unittest.TestCase):
|
||||||
|
def test_db_preserve_disabled_skips_original(self):
|
||||||
|
data = json.loads((FIXTURES / "Items_original_fixture.json").read_text(encoding="utf-8"))
|
||||||
|
result, _ = _run_search_names(data, "Items", "Items.json", preserve_original=False)
|
||||||
|
entry = result[MANIFEST["items"]["entry_index"]]
|
||||||
|
self.assertNotIn("_original", entry)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main(verbosity=2)
|
||||||
|
|
|
||||||
|
|
@ -1,363 +1,391 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Integration tests for _original source preservation in rpgmakermvmz searchCodes."""
|
"""Integration tests for _original source preservation in rpgmakermvmz searchCodes."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
os.chdir(ROOT)
|
os.chdir(ROOT)
|
||||||
sys.path.insert(0, str(ROOT))
|
sys.path.insert(0, str(ROOT))
|
||||||
|
|
||||||
import modules.rpgmakermvmz as mvmz # noqa: E402
|
import modules.rpgmakermvmz as mvmz # noqa: E402
|
||||||
|
|
||||||
LANGREGEX = mvmz.LANGREGEX
|
LANGREGEX = mvmz.LANGREGEX
|
||||||
|
|
||||||
|
|
||||||
def _mock_translate(text, history, batch=False):
|
def _mock_translate(text, history, batch=False):
|
||||||
def tr(s):
|
def tr(s):
|
||||||
if not isinstance(s, str):
|
if not isinstance(s, str):
|
||||||
return s
|
return s
|
||||||
m = re.match(r"^(\[[^\]]+\]:\s?)", s)
|
m = re.match(r"^(\[[^\]]+\]:\s?)", s)
|
||||||
if m:
|
if m:
|
||||||
return m.group(1) + "EN_TRANSLATED"
|
return m.group(1) + "EN_TRANSLATED"
|
||||||
return "EN_TRANSLATED"
|
return "EN_TRANSLATED"
|
||||||
|
|
||||||
if isinstance(text, list):
|
if isinstance(text, list):
|
||||||
return [[tr(t) for t in text], [0, 0]]
|
return [[tr(t) for t in text], [0, 0]]
|
||||||
return [tr(text), [0, 0]]
|
return [tr(text), [0, 0]]
|
||||||
|
|
||||||
|
|
||||||
def _mock_speaker(name):
|
def _mock_speaker(name):
|
||||||
return [f"Speaker_{name}", [0, 0]]
|
return [f"Speaker_{name}", [0, 0]]
|
||||||
|
|
||||||
|
|
||||||
FIXTURE_MAP = ROOT / "tests" / "fixtures" / "Map_original_fixture.json"
|
FIXTURE_MAP = ROOT / "tests" / "fixtures" / "Map_original_fixture.json"
|
||||||
FIXTURE_MANIFEST = ROOT / "tests" / "fixtures" / "Map_original_fixture_manifest.json"
|
FIXTURE_MANIFEST = ROOT / "tests" / "fixtures" / "Map_original_fixture_manifest.json"
|
||||||
CASE_MARKER_RE = re.compile(r"# CASE:(\S+)")
|
CASE_MARKER_RE = re.compile(r"# CASE:(\S+)")
|
||||||
|
|
||||||
|
|
||||||
def _load_fixture_page():
|
def _load_fixture_page():
|
||||||
data = json.loads(FIXTURE_MAP.read_text(encoding="utf-8-sig"))
|
data = json.loads(FIXTURE_MAP.read_text(encoding="utf-8-sig"))
|
||||||
event = next(e for e in data["events"] if e and e.get("id") == 1)
|
event = next(e for e in data["events"] if e and e.get("id") == 1)
|
||||||
return {"list": copy.deepcopy(event["pages"][0]["list"])}
|
return {"list": copy.deepcopy(event["pages"][0]["list"])}
|
||||||
|
|
||||||
|
|
||||||
def _case_commands(page_list):
|
def _case_commands(page_list):
|
||||||
"""Map manifest case id -> command immediately following its 108 marker."""
|
"""Map manifest case id -> command immediately following its 108 marker."""
|
||||||
cases = {}
|
cases = {}
|
||||||
pending = None
|
pending = None
|
||||||
for cmd in page_list:
|
for cmd in page_list:
|
||||||
if not cmd:
|
if not cmd:
|
||||||
continue
|
continue
|
||||||
if cmd.get("code") == 108:
|
if cmd.get("code") == 108:
|
||||||
m = CASE_MARKER_RE.search(str(cmd.get("parameters", [""])[0]))
|
m = CASE_MARKER_RE.search(str(cmd.get("parameters", [""])[0]))
|
||||||
pending = m.group(1) if m else None
|
pending = m.group(1) if m else None
|
||||||
continue
|
continue
|
||||||
if pending:
|
if pending:
|
||||||
cases[pending] = cmd
|
cases[pending] = cmd
|
||||||
pending = None
|
pending = None
|
||||||
return cases
|
return cases
|
||||||
|
|
||||||
|
|
||||||
def _load_fixture_manifest():
|
def _load_fixture_manifest():
|
||||||
return json.loads(FIXTURE_MANIFEST.read_text(encoding="utf-8"))
|
return json.loads(FIXTURE_MANIFEST.read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
|
||||||
def _load_map_excerpt():
|
def _load_map_excerpt():
|
||||||
"""Small real snippets from Map002 event 17 + Map001 102 + synthetic 101/122."""
|
"""Small real snippets from Map002 event 17 + Map001 102 + synthetic 101/122."""
|
||||||
map2 = json.loads((ROOT / "files" / "Map002.json").read_text(encoding="utf-8-sig"))
|
map2 = json.loads((ROOT / "files" / "Map002.json").read_text(encoding="utf-8-sig"))
|
||||||
ev17 = next(e for e in map2["events"] if e and e.get("id") == 17)
|
ev17 = next(e for e in map2["events"] if e and e.get("id") == 17)
|
||||||
real = copy.deepcopy(ev17["pages"][0]["list"][7:14]) # 101 + 401 dialogue block
|
real = copy.deepcopy(ev17["pages"][0]["list"][7:14]) # 101 + 401 dialogue block
|
||||||
|
|
||||||
map1 = json.loads((ROOT / "files" / "Map001.json").read_text(encoding="utf-8-sig"))
|
map1 = json.loads((ROOT / "files" / "Map001.json").read_text(encoding="utf-8-sig"))
|
||||||
choice_cmd = None
|
choice_cmd = None
|
||||||
for ev in map1["events"]:
|
for ev in map1["events"]:
|
||||||
if not ev:
|
if not ev:
|
||||||
continue
|
continue
|
||||||
for pg in ev.get("pages") or []:
|
for pg in ev.get("pages") or []:
|
||||||
if not pg:
|
if not pg:
|
||||||
continue
|
continue
|
||||||
for cmd in pg.get("list") or []:
|
for cmd in pg.get("list") or []:
|
||||||
if cmd and cmd.get("code") == 102:
|
if cmd and cmd.get("code") == 102:
|
||||||
choice_cmd = copy.deepcopy(cmd)
|
choice_cmd = copy.deepcopy(cmd)
|
||||||
break
|
break
|
||||||
if choice_cmd:
|
if choice_cmd:
|
||||||
break
|
break
|
||||||
if choice_cmd:
|
if choice_cmd:
|
||||||
break
|
break
|
||||||
assert choice_cmd is not None, "Map001 should contain a 102 choice command"
|
assert choice_cmd is not None, "Map001 should contain a 102 choice command"
|
||||||
|
|
||||||
synthetic = [
|
synthetic = [
|
||||||
{"code": 101, "indent": 0, "parameters": ["", 0, 0, 2, "\\C[2]アリス\\C[0]"]},
|
{"code": 101, "indent": 0, "parameters": ["", 0, 0, 2, "\\C[2]アリス\\C[0]"]},
|
||||||
{"code": 401, "indent": 0, "parameters": ["こんにちは、世界。"]},
|
{"code": 401, "indent": 0, "parameters": ["こんにちは、世界。"]},
|
||||||
{
|
{
|
||||||
"code": 122,
|
"code": 122,
|
||||||
"indent": 0,
|
"indent": 0,
|
||||||
"parameters": [101, 101, 0, 0, "`変数テスト`"],
|
"parameters": [101, 101, 0, 0, "`変数テスト`"],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
return {"list": real + [choice_cmd] + synthetic}
|
return {"list": real + [choice_cmd] + synthetic}
|
||||||
|
|
||||||
|
|
||||||
def _resolve_case_command(page_list, entry, marked_cases=None):
|
def _resolve_case_command(page_list, entry, marked_cases=None):
|
||||||
"""Find manifest case command by CASE marker or by code + expected _original."""
|
"""Find manifest case command by CASE marker or by code + expected _original."""
|
||||||
marked_cases = marked_cases if marked_cases is not None else _case_commands(page_list)
|
marked_cases = marked_cases if marked_cases is not None else _case_commands(page_list)
|
||||||
cid = entry["id"]
|
cid = entry["id"]
|
||||||
cmd = marked_cases.get(cid)
|
cmd = marked_cases.get(cid)
|
||||||
if cmd is not None and cmd.get("code") == entry.get("code"):
|
if cmd is not None and cmd.get("code") == entry.get("code"):
|
||||||
return cmd
|
return cmd
|
||||||
exp = entry.get("expected_original")
|
exp = entry.get("expected_original")
|
||||||
code = entry.get("code")
|
code = entry.get("code")
|
||||||
if code is not None and exp is not None:
|
if code is not None and exp is not None:
|
||||||
for candidate in page_list:
|
for candidate in page_list:
|
||||||
if candidate and candidate.get("code") == code and candidate.get("_original") == exp:
|
if candidate and candidate.get("code") == code and candidate.get("_original") == exp:
|
||||||
return candidate
|
return candidate
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
def _run_search_codes(page):
|
def _run_search_codes(page, *, preserve_original=True):
|
||||||
"""Full Pass 1 -> mock translate -> Pass 2 cycle."""
|
"""Full Pass 1 -> mock translate -> Pass 2 cycle."""
|
||||||
captured = []
|
captured = []
|
||||||
|
|
||||||
def translate(text, history, batch=False):
|
def translate(text, history, batch=False):
|
||||||
captured.append(copy.deepcopy(text))
|
captured.append(copy.deepcopy(text))
|
||||||
return _mock_translate(text, history, batch)
|
return _mock_translate(text, history, batch)
|
||||||
|
|
||||||
def speaker(name):
|
def speaker(name):
|
||||||
return _mock_speaker(name)
|
return _mock_speaker(name)
|
||||||
|
|
||||||
orig_t = mvmz.translateAI
|
orig_t = mvmz.translateAI
|
||||||
orig_s = mvmz.getSpeaker
|
orig_s = mvmz.getSpeaker
|
||||||
orig_122 = mvmz.CODE122
|
orig_122 = mvmz.CODE122
|
||||||
orig_408 = mvmz.CODE408
|
orig_408 = mvmz.CODE408
|
||||||
mvmz.translateAI = translate
|
orig_101 = mvmz.CODE101
|
||||||
mvmz.getSpeaker = speaker
|
orig_401 = mvmz.CODE401
|
||||||
mvmz.CODE122 = True
|
orig_405 = mvmz.CODE405
|
||||||
mvmz.CODE408 = True
|
orig_102 = mvmz.CODE102
|
||||||
try:
|
orig_preserve = mvmz.PRESERVEORIGINAL
|
||||||
page_copy = copy.deepcopy(page)
|
mvmz.translateAI = translate
|
||||||
mvmz.searchCodes(page_copy, None, [], "TestMap.json")
|
mvmz.getSpeaker = speaker
|
||||||
return page_copy, captured
|
mvmz.CODE122 = True
|
||||||
finally:
|
mvmz.CODE408 = True
|
||||||
mvmz.translateAI = orig_t
|
mvmz.CODE101 = True
|
||||||
mvmz.getSpeaker = orig_s
|
mvmz.CODE401 = True
|
||||||
mvmz.CODE122 = orig_122
|
mvmz.CODE405 = True
|
||||||
mvmz.CODE408 = orig_408
|
mvmz.CODE102 = True
|
||||||
|
mvmz.PRESERVEORIGINAL = preserve_original
|
||||||
|
try:
|
||||||
def _find_commands(page, code):
|
page_copy = copy.deepcopy(page)
|
||||||
return [cmd for cmd in page["list"] if cmd and cmd.get("code") == code]
|
mvmz.searchCodes(page_copy, None, [], "TestMap.json")
|
||||||
|
return page_copy, captured
|
||||||
|
finally:
|
||||||
def _has_japanese(s: str) -> bool:
|
mvmz.translateAI = orig_t
|
||||||
return bool(re.search(LANGREGEX, s or ""))
|
mvmz.getSpeaker = orig_s
|
||||||
|
mvmz.CODE122 = orig_122
|
||||||
|
mvmz.CODE408 = orig_408
|
||||||
class TestMVMZSourceOriginal(unittest.TestCase):
|
mvmz.CODE101 = orig_101
|
||||||
def test_first_pass_writes_original(self):
|
mvmz.CODE401 = orig_401
|
||||||
page, _ = _run_search_codes(_load_map_excerpt())
|
mvmz.CODE405 = orig_405
|
||||||
|
mvmz.CODE102 = orig_102
|
||||||
cmds401 = _find_commands(page, 401)
|
mvmz.PRESERVEORIGINAL = orig_preserve
|
||||||
with_orig = [c for c in cmds401 if c.get("_original")]
|
|
||||||
self.assertGreater(len(with_orig), 0, "401 dialogue should have _original")
|
|
||||||
for c in with_orig:
|
def _find_commands(page, code):
|
||||||
self.assertTrue(_has_japanese(c["_original"]))
|
return [cmd for cmd in page["list"] if cmd and cmd.get("code") == code]
|
||||||
self.assertNotEqual(c["parameters"][0], c["_original"])
|
|
||||||
|
|
||||||
c102 = _find_commands(page, 102)[0]
|
def _has_japanese(s: str) -> bool:
|
||||||
self.assertIsInstance(c102.get("_original"), list)
|
return bool(re.search(LANGREGEX, s or ""))
|
||||||
self.assertEqual(len(c102["_original"]), len(c102["parameters"][0]))
|
|
||||||
for i, orig in enumerate(c102["_original"]):
|
|
||||||
if orig:
|
class TestMVMZSourceOriginal(unittest.TestCase):
|
||||||
self.assertTrue(_has_japanese(orig), f"choice {i} _original should be Japanese")
|
def test_first_pass_writes_original(self):
|
||||||
self.assertNotEqual(c102["parameters"][0][i], orig)
|
page, _ = _run_search_codes(_load_map_excerpt())
|
||||||
|
|
||||||
c101 = next(c for c in _find_commands(page, 101) if len(c.get("parameters", [])) > 4)
|
cmds401 = _find_commands(page, 401)
|
||||||
self.assertIn("_original", c101)
|
with_orig = [c for c in cmds401 if c.get("_original")]
|
||||||
self.assertIn("アリス", c101["_original"])
|
self.assertGreater(len(with_orig), 0, "401 dialogue should have _original")
|
||||||
self.assertIn("\\C[2]", c101["_original"])
|
for c in with_orig:
|
||||||
self.assertIn("Speaker_", c101["parameters"][4])
|
self.assertTrue(_has_japanese(c["_original"]))
|
||||||
|
self.assertNotEqual(c["parameters"][0], c["_original"])
|
||||||
c122 = _find_commands(page, 122)[0]
|
|
||||||
self.assertEqual(c122["_original"], "変数テスト")
|
c102 = _find_commands(page, 102)[0]
|
||||||
self.assertIn("EN_TRANSLATED", c122["parameters"][4])
|
self.assertIsInstance(c102.get("_original"), list)
|
||||||
|
self.assertEqual(len(c102["_original"]), len(c102["parameters"][0]))
|
||||||
def test_rerun_uses_original_not_display_text(self):
|
for i, orig in enumerate(c102["_original"]):
|
||||||
page1, captured1 = _run_search_codes(_load_map_excerpt())
|
if orig:
|
||||||
originals_snapshot = json.dumps(
|
self.assertTrue(_has_japanese(orig), f"choice {i} _original should be Japanese")
|
||||||
{i: cmd.get("_original") for i, cmd in enumerate(page1["list"]) if cmd},
|
self.assertNotEqual(c102["parameters"][0][i], orig)
|
||||||
ensure_ascii=False,
|
|
||||||
)
|
c101 = next(c for c in _find_commands(page, 101) if len(c.get("parameters", [])) > 4)
|
||||||
|
self.assertIn("_original", c101)
|
||||||
page2, captured2 = _run_search_codes(page1)
|
self.assertIn("アリス", c101["_original"])
|
||||||
originals_after = json.dumps(
|
self.assertIn("\\C[2]", c101["_original"])
|
||||||
{i: cmd.get("_original") for i, cmd in enumerate(page2["list"]) if cmd},
|
self.assertIn("Speaker_", c101["parameters"][4])
|
||||||
ensure_ascii=False,
|
|
||||||
)
|
c122 = _find_commands(page, 122)[0]
|
||||||
self.assertEqual(originals_snapshot, originals_after, "_original must not change on re-run")
|
self.assertEqual(c122["_original"], "変数テスト")
|
||||||
|
self.assertIn("EN_TRANSLATED", c122["parameters"][4])
|
||||||
# Every batch sent to translateAI on re-run should still contain Japanese
|
|
||||||
for payload in captured2:
|
def test_rerun_uses_original_not_display_text(self):
|
||||||
items = payload if isinstance(payload, list) else [payload]
|
page1, captured1 = _run_search_codes(_load_map_excerpt())
|
||||||
for item in items:
|
originals_snapshot = json.dumps(
|
||||||
if not isinstance(item, str):
|
{i: cmd.get("_original") for i, cmd in enumerate(page1["list"]) if cmd},
|
||||||
continue
|
ensure_ascii=False,
|
||||||
if item == "EN_TRANSLATED":
|
)
|
||||||
continue
|
|
||||||
if _has_japanese(item):
|
page2, captured2 = _run_search_codes(page1)
|
||||||
continue
|
originals_after = json.dumps(
|
||||||
if re.match(r"^\[.+?\]:\s*EN_TRANSLATED$", item):
|
{i: cmd.get("_original") for i, cmd in enumerate(page2["list"]) if cmd},
|
||||||
continue
|
ensure_ascii=False,
|
||||||
self.fail(f"Re-run sent non-Japanese to translateAI: {item!r}")
|
)
|
||||||
|
self.assertEqual(originals_snapshot, originals_after, "_original must not change on re-run")
|
||||||
def test_map002_micro_page_real_data(self):
|
|
||||||
"""Translate a tiny slice of Map002 event 17 only (7 commands)."""
|
# Every batch sent to translateAI on re-run should still contain Japanese
|
||||||
map2 = json.loads((ROOT / "files" / "Map002.json").read_text(encoding="utf-8-sig"))
|
for payload in captured2:
|
||||||
ev17 = next(e for e in map2["events"] if e and e.get("id") == 17)
|
items = payload if isinstance(payload, list) else [payload]
|
||||||
micro = {"list": copy.deepcopy(ev17["pages"][0]["list"][8:11])} # 3x401 only
|
for item in items:
|
||||||
|
if not isinstance(item, str):
|
||||||
page, _ = _run_search_codes(micro)
|
continue
|
||||||
c401 = _find_commands(page, 401)
|
if item == "EN_TRANSLATED":
|
||||||
self.assertGreaterEqual(len(c401), 1)
|
continue
|
||||||
with_orig = [c for c in c401 if c.get("_original")]
|
if _has_japanese(item):
|
||||||
self.assertGreaterEqual(len(with_orig), 1)
|
continue
|
||||||
for c in with_orig:
|
if re.match(r"^\[.+?\]:\s*EN_TRANSLATED$", item):
|
||||||
self.assertTrue(_has_japanese(c["_original"]))
|
continue
|
||||||
|
self.fail(f"Re-run sent non-Japanese to translateAI: {item!r}")
|
||||||
def test_speaker_color_line_full_original(self):
|
|
||||||
"""Standalone \\C[n]Name\\C[n] speaker 401 lines keep the full string in _original."""
|
def test_map002_micro_page_real_data(self):
|
||||||
page = {
|
"""Translate a tiny slice of Map002 event 17 only (7 commands)."""
|
||||||
"list": [
|
map2 = json.loads((ROOT / "files" / "Map002.json").read_text(encoding="utf-8-sig"))
|
||||||
{"code": 401, "indent": 0, "parameters": ["\\C[2]エルーシャ\\C[0]"]},
|
ev17 = next(e for e in map2["events"] if e and e.get("id") == 17)
|
||||||
{"code": 401, "indent": 0, "parameters": ["「テストセリフ」"]},
|
micro = {"list": copy.deepcopy(ev17["pages"][0]["list"][8:11])} # 3x401 only
|
||||||
]
|
|
||||||
}
|
page, _ = _run_search_codes(micro)
|
||||||
page, _ = _run_search_codes(page)
|
c401 = _find_commands(page, 401)
|
||||||
speaker_cmd = page["list"][0]
|
self.assertGreaterEqual(len(c401), 1)
|
||||||
self.assertEqual(speaker_cmd.get("_original"), "\\C[2]エルーシャ\\C[0]")
|
with_orig = [c for c in c401 if c.get("_original")]
|
||||||
self.assertIn("\\C[2]", speaker_cmd["parameters"][0])
|
self.assertGreaterEqual(len(with_orig), 1)
|
||||||
self.assertIn("Speaker_エルーシャ", speaker_cmd["parameters"][0])
|
for c in with_orig:
|
||||||
|
self.assertTrue(_has_japanese(c["_original"]))
|
||||||
# Re-run: _original unchanged, getSpeaker still receives Japanese name
|
|
||||||
speakers_seen = []
|
def test_speaker_color_line_full_original(self):
|
||||||
|
"""Standalone \\C[n]Name\\C[n] speaker 401 lines keep the full string in _original."""
|
||||||
def speaker(name):
|
page = {
|
||||||
speakers_seen.append(name)
|
"list": [
|
||||||
return _mock_speaker(name)
|
{"code": 401, "indent": 0, "parameters": ["\\C[2]エルーシャ\\C[0]"]},
|
||||||
|
{"code": 401, "indent": 0, "parameters": ["「テストセリフ」"]},
|
||||||
orig_t, orig_s = mvmz.translateAI, mvmz.getSpeaker
|
]
|
||||||
mvmz.getSpeaker = speaker
|
}
|
||||||
mvmz.translateAI = lambda text, history, batch=False: _mock_translate(text, history, batch)
|
page, _ = _run_search_codes(page)
|
||||||
try:
|
speaker_cmd = page["list"][0]
|
||||||
mvmz.searchCodes(page, None, [], "TestMap.json")
|
self.assertEqual(speaker_cmd.get("_original"), "\\C[2]エルーシャ\\C[0]")
|
||||||
finally:
|
self.assertIn("\\C[2]", speaker_cmd["parameters"][0])
|
||||||
mvmz.getSpeaker = orig_s
|
self.assertIn("Speaker_エルーシャ", speaker_cmd["parameters"][0])
|
||||||
mvmz.translateAI = orig_t
|
|
||||||
self.assertEqual(speaker_cmd["_original"], "\\C[2]エルーシャ\\C[0]")
|
# Re-run: _original unchanged, getSpeaker still receives Japanese name
|
||||||
self.assertIn("エルーシャ", speakers_seen)
|
speakers_seen = []
|
||||||
|
|
||||||
def test_405_split_rerun_uses_anchor_original_only(self):
|
def speaker(name):
|
||||||
"""English 405 siblings after a split must not pollute re-run source."""
|
speakers_seen.append(name)
|
||||||
page = {
|
return _mock_speaker(name)
|
||||||
"list": [
|
|
||||||
{
|
orig_t, orig_s = mvmz.translateAI, mvmz.getSpeaker
|
||||||
"code": 405,
|
orig_401 = mvmz.CODE401
|
||||||
"indent": 0,
|
mvmz.getSpeaker = speaker
|
||||||
"parameters": ["EN_LINE_1"],
|
mvmz.CODE401 = True
|
||||||
"_original": "第一行\n第二行",
|
mvmz.translateAI = lambda text, history, batch=False: _mock_translate(text, history, batch)
|
||||||
},
|
try:
|
||||||
{"code": 405, "indent": 0, "parameters": ["EN_LINE_2"]},
|
mvmz.searchCodes(page, None, [], "TestMap.json")
|
||||||
]
|
finally:
|
||||||
}
|
mvmz.getSpeaker = orig_s
|
||||||
_, captured = _run_search_codes(page)
|
mvmz.translateAI = orig_t
|
||||||
self.assertGreater(len(captured), 0)
|
mvmz.CODE401 = orig_401
|
||||||
payloads = captured if isinstance(captured[0], str) else captured
|
self.assertEqual(speaker_cmd["_original"], "\\C[2]エルーシャ\\C[0]")
|
||||||
for payload in payloads:
|
self.assertIn("エルーシャ", speakers_seen)
|
||||||
if not isinstance(payload, str):
|
|
||||||
continue
|
def test_405_split_rerun_uses_anchor_original_only(self):
|
||||||
self.assertIn("第一行", payload)
|
"""English 405 siblings after a split must not pollute re-run source."""
|
||||||
self.assertNotIn("EN_LINE_1", payload)
|
page = {
|
||||||
self.assertNotIn("EN_LINE_2", payload)
|
"list": [
|
||||||
|
{
|
||||||
|
"code": 405,
|
||||||
def test_408_choice_help_original(self):
|
"indent": 0,
|
||||||
page = {
|
"parameters": ["EN_LINE_1"],
|
||||||
"list": [
|
"_original": "第一行\n第二行",
|
||||||
{"code": 108, "indent": 0, "parameters": ["選択肢ヘルプ"]},
|
},
|
||||||
{"code": 408, "indent": 0, "parameters": ["これは選択肢のヘルプです。"]},
|
{"code": 405, "indent": 0, "parameters": ["EN_LINE_2"]},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
page, captured = _run_search_codes(page)
|
_, captured = _run_search_codes(page)
|
||||||
cmd = _find_commands(page, 408)[0]
|
self.assertGreater(len(captured), 0)
|
||||||
self.assertEqual(cmd.get("_original"), "これは選択肢のヘルプです。")
|
payloads = captured if isinstance(captured[0], str) else captured
|
||||||
self.assertNotEqual(cmd["parameters"][0], cmd["_original"])
|
for payload in payloads:
|
||||||
self.assertGreater(len(captured), 0)
|
if not isinstance(payload, str):
|
||||||
|
continue
|
||||||
page2, captured2 = _run_search_codes(page)
|
self.assertIn("第一行", payload)
|
||||||
self.assertEqual(cmd["_original"], _find_commands(page2, 408)[0]["_original"])
|
self.assertNotIn("EN_LINE_1", payload)
|
||||||
for payload in captured2:
|
self.assertNotIn("EN_LINE_2", payload)
|
||||||
items = payload if isinstance(payload, list) else [payload]
|
|
||||||
for item in items:
|
|
||||||
if not isinstance(item, str) or item == "EN_TRANSLATED":
|
def test_408_choice_help_original(self):
|
||||||
continue
|
page = {
|
||||||
self.assertTrue(_has_japanese(item), f"408 re-run sent non-Japanese: {item!r}")
|
"list": [
|
||||||
|
{"code": 108, "indent": 0, "parameters": ["選択肢ヘルプ"]},
|
||||||
|
{"code": 408, "indent": 0, "parameters": ["これは選択肢のヘルプです。"]},
|
||||||
class TestFixtureMapOriginal(unittest.TestCase):
|
]
|
||||||
"""Full fixture map covering every _original preservation code path."""
|
}
|
||||||
|
page, captured = _run_search_codes(page)
|
||||||
def test_fixture_all_cases_original(self):
|
cmd = _find_commands(page, 408)[0]
|
||||||
page, _ = _run_search_codes(_load_fixture_page())
|
self.assertEqual(cmd.get("_original"), "これは選択肢のヘルプです。")
|
||||||
marked = _case_commands(page["list"])
|
self.assertNotEqual(cmd["parameters"][0], cmd["_original"])
|
||||||
manifest = _load_fixture_manifest()
|
self.assertGreater(len(captured), 0)
|
||||||
|
|
||||||
for entry in manifest["cases"]:
|
page2, captured2 = _run_search_codes(page)
|
||||||
cid = entry["id"]
|
self.assertEqual(cmd["_original"], _find_commands(page2, 408)[0]["_original"])
|
||||||
cmd = _resolve_case_command(page["list"], entry, marked)
|
for payload in captured2:
|
||||||
expected = entry["expected_original"]
|
items = payload if isinstance(payload, list) else [payload]
|
||||||
with self.subTest(case=cid):
|
for item in items:
|
||||||
self.assertIsNotNone(cmd, f"could not resolve fixture case {cid}")
|
if not isinstance(item, str) or item == "EN_TRANSLATED":
|
||||||
self.assertEqual(cmd.get("_original"), expected, entry.get("summary", cid))
|
continue
|
||||||
if isinstance(expected, str):
|
self.assertTrue(_has_japanese(item), f"408 re-run sent non-Japanese: {item!r}")
|
||||||
self.assertTrue(_has_japanese(expected))
|
|
||||||
if cmd.get("parameters"):
|
|
||||||
display = cmd["parameters"][0]
|
class TestFixtureMapOriginal(unittest.TestCase):
|
||||||
if isinstance(display, str):
|
"""Full fixture map covering every _original preservation code path."""
|
||||||
self.assertNotEqual(display, expected)
|
|
||||||
|
def test_fixture_all_cases_original(self):
|
||||||
def test_fixture_rerun_preserves_original(self):
|
page, _ = _run_search_codes(_load_fixture_page())
|
||||||
page1, _ = _run_search_codes(_load_fixture_page())
|
marked = _case_commands(page["list"])
|
||||||
page2, captured2 = _run_search_codes(page1)
|
manifest = _load_fixture_manifest()
|
||||||
manifest = _load_fixture_manifest()
|
|
||||||
|
for entry in manifest["cases"]:
|
||||||
for entry in manifest["cases"]:
|
cid = entry["id"]
|
||||||
cid = entry["id"]
|
cmd = _resolve_case_command(page["list"], entry, marked)
|
||||||
cmd1 = _resolve_case_command(page1["list"], entry)
|
expected = entry["expected_original"]
|
||||||
cmd2 = _resolve_case_command(page2["list"], entry)
|
with self.subTest(case=cid):
|
||||||
with self.subTest(case=cid):
|
self.assertIsNotNone(cmd, f"could not resolve fixture case {cid}")
|
||||||
self.assertIsNotNone(cmd1)
|
self.assertEqual(cmd.get("_original"), expected, entry.get("summary", cid))
|
||||||
self.assertIsNotNone(cmd2)
|
if isinstance(expected, str):
|
||||||
self.assertEqual(cmd1.get("_original"), cmd2.get("_original"))
|
self.assertTrue(_has_japanese(expected))
|
||||||
|
if cmd.get("parameters"):
|
||||||
for payload in captured2:
|
display = cmd["parameters"][0]
|
||||||
items = payload if isinstance(payload, list) else [payload]
|
if isinstance(display, str):
|
||||||
for item in items:
|
self.assertNotEqual(display, expected)
|
||||||
if not isinstance(item, str) or item == "EN_TRANSLATED":
|
|
||||||
continue
|
def test_fixture_rerun_preserves_original(self):
|
||||||
if _has_japanese(item):
|
page1, _ = _run_search_codes(_load_fixture_page())
|
||||||
continue
|
page2, captured2 = _run_search_codes(page1)
|
||||||
if re.match(r"^\[.+?\]:\s*EN_TRANSLATED$", item):
|
manifest = _load_fixture_manifest()
|
||||||
continue
|
|
||||||
self.fail(f"Fixture re-run sent non-Japanese to translateAI: {item!r}")
|
for entry in manifest["cases"]:
|
||||||
|
cid = entry["id"]
|
||||||
|
cmd1 = _resolve_case_command(page1["list"], entry)
|
||||||
if __name__ == "__main__":
|
cmd2 = _resolve_case_command(page2["list"], entry)
|
||||||
unittest.main(verbosity=2)
|
with self.subTest(case=cid):
|
||||||
|
self.assertIsNotNone(cmd1)
|
||||||
|
self.assertIsNotNone(cmd2)
|
||||||
|
self.assertEqual(cmd1.get("_original"), cmd2.get("_original"))
|
||||||
|
|
||||||
|
for payload in captured2:
|
||||||
|
items = payload if isinstance(payload, list) else [payload]
|
||||||
|
for item in items:
|
||||||
|
if not isinstance(item, str) or item == "EN_TRANSLATED":
|
||||||
|
continue
|
||||||
|
if _has_japanese(item):
|
||||||
|
continue
|
||||||
|
if re.match(r"^\[.+?\]:\s*EN_TRANSLATED$", item):
|
||||||
|
continue
|
||||||
|
self.fail(f"Fixture re-run sent non-Japanese to translateAI: {item!r}")
|
||||||
|
|
||||||
|
def test_preserve_original_codes_disabled(self):
|
||||||
|
"""PRESERVEORIGINAL=False must not write _original on map commands."""
|
||||||
|
page = {
|
||||||
|
"list": [
|
||||||
|
{"code": 401, "indent": 0, "parameters": ["こんにちは"]},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
page, _ = _run_search_codes(page, preserve_original=False)
|
||||||
|
self.assertNotIn("_original", page["list"][0])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main(verbosity=2)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue