#!/usr/bin/env python3 """ Migration script to convert wardrobe structure from flat to nested format. Before: "wardrobe": { "headwear": "...", "top": "...", ... } After: "wardrobe": { "default": { "headwear": "...", "top": "...", ... } } This enables multiple outfits per character. """ import os import json from pathlib import Path def migrate_wardrobe(characters_dir: str = "characters", dry_run: bool = False): """ Migrate all character JSON files to the new wardrobe structure. Args: characters_dir: Path to the directory containing character JSON files dry_run: If True, only print what would be changed without modifying files """ characters_path = Path(characters_dir) if not characters_path.exists(): print(f"Error: Directory '{characters_dir}' does not exist") return json_files = list(characters_path.glob("*.json")) if not json_files: print(f"No JSON files found in '{characters_dir}'") return migrated_count = 0 skipped_count = 0 error_count = 0 for json_file in json_files: try: with open(json_file, 'r', encoding='utf-8') as f: data = json.load(f) # Check if character has a wardrobe if 'wardrobe' not in data: print(f" [SKIP] {json_file.name}: No wardrobe field") skipped_count += 1 continue wardrobe = data['wardrobe'] # Check if already migrated (wardrobe contains 'default' key with nested dict) if 'default' in wardrobe and isinstance(wardrobe['default'], dict): # Verify it's actually the new format (has wardrobe keys inside) expected_keys = {'headwear', 'top', 'legwear', 'footwear', 'hands', 'accessories', 'inner_layer', 'outer_layer', 'lower_body', 'gloves'} if any(key in wardrobe['default'] for key in expected_keys): print(f" [SKIP] {json_file.name}: Already migrated") skipped_count += 1 continue # Check if wardrobe is a flat structure (not already nested) # A flat wardrobe has string values, a nested one has dict values if not isinstance(wardrobe, dict): print(f" [ERROR] {json_file.name}: Wardrobe is not a dictionary") error_count += 1 continue # Check if any value is a dict (indicating partial migration or different structure) has_nested_values = any(isinstance(v, dict) for v in wardrobe.values()) if has_nested_values: print(f" [SKIP] {json_file.name}: Wardrobe has nested values, may already be migrated") skipped_count += 1 continue # Perform migration new_wardrobe = { "default": wardrobe } data['wardrobe'] = new_wardrobe if dry_run: print(f" [DRY-RUN] {json_file.name}: Would migrate wardrobe") print(f" Old: {json.dumps(wardrobe, indent=2)[:100]}...") print(f" New: {json.dumps(new_wardrobe, indent=2)[:100]}...") else: with open(json_file, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) print(f" [MIGRATED] {json_file.name}") migrated_count += 1 except json.JSONDecodeError as e: print(f" [ERROR] {json_file.name}: Invalid JSON - {e}") error_count += 1 except Exception as e: print(f" [ERROR] {json_file.name}: {e}") error_count += 1 print() print("=" * 50) print(f"Migration complete:") print(f" - Migrated: {migrated_count}") print(f" - Skipped: {skipped_count}") print(f" - Errors: {error_count}") if dry_run: print() print("This was a dry run. No files were modified.") print("Run with --execute to apply changes.") if __name__ == "__main__": import argparse parser = argparse.ArgumentParser( description="Migrate character wardrobe structure to support multiple outfits" ) parser.add_argument( "--execute", action="store_true", help="Actually modify files (default is dry-run)" ) parser.add_argument( "--dir", default="characters", help="Directory containing character JSON files (default: characters)" ) args = parser.parse_args() print("=" * 50) print("Wardrobe Migration Script") print("=" * 50) print(f"Directory: {args.dir}") print(f"Mode: {'EXECUTE' if args.execute else 'DRY-RUN'}") print("=" * 50) print() migrate_wardrobe(characters_dir=args.dir, dry_run=not args.execute)